Antharee commited on
Commit
aab3b7f
·
verified ·
1 Parent(s): 0d1f6bf

Upload 18 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,16 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/1752755344481.jpg filter=lfs diff=lfs merge=lfs -text
37
+ static/1753071647_ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
38
+ static/1753072175_ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
39
+ static/1753072439_ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
40
+ static/1753072499_1752755344481.jpg filter=lfs diff=lfs merge=lfs -text
41
+ static/1753072773_ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
42
+ static/1753086977_ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
43
+ static/1753088075_1752755344481.jpg filter=lfs diff=lfs merge=lfs -text
44
+ static/card2.webp filter=lfs diff=lfs merge=lfs -text
45
+ static/maxresdefault_processed.jpg filter=lfs diff=lfs merge=lfs -text
46
+ static/maxresdefault.jpg filter=lfs diff=lfs merge=lfs -text
47
+ static/ThaiIDCard_Mr._Sample500x500_processed.jpg filter=lfs diff=lfs merge=lfs -text
48
+ static/ผลแล็ป.png filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, render_template, jsonify
2
+ import easyocr
3
+ import google.generativeai as genai
4
+ import os
5
+ import re
6
+
7
+ app = Flask(__name__)
8
+
9
+ # ตั้งค่า Gemini API Key
10
+ genai.configure(api_key="AIzaSyCK_4YXWSEaZf6_E0RS_kNIsvHRBbAz8rQ") # เปลี่ยนตรงนี้ให้เป็น API Key ของคุณ
11
+
12
+ # โหลดโมเดล OCR
13
+ reader = easyocr.Reader(['th', 'en'])
14
+
15
+ # โหลดโมเดล Gemini
16
+ model = genai.GenerativeModel("gemini-2.0-flash")
17
+
18
+ # สร้างโฟลเดอร์สำหรับเก็บไฟล์อัปโหลด
19
+ if not os.path.exists('static'):
20
+ os.makedirs('static')
21
+
22
+ # ฟังก์ชันให้ Gemini ช่วยแก้ข้อความ OCR
23
+ def refine_text_with_gemini(text):
24
+ try:
25
+ prompt = f"""
26
+ ด้านล่างนี้คือข้อความดิบที่ได้จาก OCR ซึ่งอาจมีคำผิดหรือรูปแบบไม่ถูกต้อง:
27
+ {text}
28
+
29
+ กรุณาช่วยแก้ไขให้ข้อความถูกต้องตามความเป็นจริงของบัตรประชาชนไทย:
30
+ - แก้คำสะกดผิด
31
+ - หากข้อความหายไปหรือผิดพลาดจาก OCR กรุณาเติมให้สมบูรณ์ โดยอิงจากบริบทของข้อความที่เหลือ
32
+ - ข้อมูลใดไม่ทราบข้อมูลได้ไม่ต้องแสดง
33
+ - ไม่ต้องอธิบายการทำงานเเค่ถอดข้อความออกมาให้อ่านเข้าใจ
34
+ - ไม่ต้องเเปลภาษา
35
+ """
36
+ response = model.generate_content(prompt)
37
+ return response.text
38
+ except Exception as e:
39
+ print(f"Error in Gemini processing: {e}")
40
+ return text # Return original text if Gemini fails
41
+
42
+ # ฟังก์ชันแยกข้อมูลจากการตอบของ Gemini
43
+ def parse_gemini_response(text):
44
+ """
45
+ แยกข้อมูลสำคัญจากข้อความที่ Gemini แก้ไขแล้ว
46
+ """
47
+ data = {
48
+ 'fullname': '',
49
+ 'idnumber': '',
50
+ 'birthdate': '',
51
+ 'religion': '',
52
+ 'address': '',
53
+ 'issuedate': '',
54
+ 'expiredate': ''
55
+ }
56
+
57
+ try:
58
+ # ใช้ regex patterns หาข้อมูล
59
+ lines = text.split('\n')
60
+
61
+ for line in lines:
62
+ line = line.strip()
63
+ # หาชื่อ-นามสกุล (มักจะเป็นบรรทัดที่มีคำว่า "นาย" "นาง" "นางสาว" หรือเป็นชื่อภาษาไทย)
64
+ if re.search(r'(นาย|นาง|นางสาว|Mr\.|Mrs\.|Miss)', line):
65
+ data['fullname'] = line
66
+
67
+ # หาเลขประจำตัวประชาชน (13 หลัก)
68
+ id_match = re.search(r'(\d{1}\s*\d{4}\s*\d{5}\s*\d{2}\s*\d{1}|\d{13})', line)
69
+ if id_match:
70
+ data['idnumber'] = id_match.group().replace(' ', '')
71
+
72
+ # หาวันเกิด
73
+ if re.search(r'(เกิด|born)', line.lower()):
74
+ date_match = re.search(r'(\d{1,2}[\s/.-]\d{1,2}[\s/.-]\d{4})', line)
75
+ if date_match:
76
+ data['birthdate'] = date_match.group()
77
+
78
+ # หาศาสนา
79
+ if re.search(r'(ศาสนา|religion)', line.lower()):
80
+ religion_match = re.search(r'(พุทธ|คริสต์|อิสลาม|ฮินดู|ซิกข์)', line)
81
+ if religion_match:
82
+ data['religion'] = religion_match.group()
83
+
84
+ # หาที่อยู่ (บรรทัดที่มีเลขที่ หมู่ ถนน)
85
+ if re.search(r'(เลขที่|หมู่|ถนน|ตำบล|อำเภอ|จังหวัด)', line):
86
+ if not data['address']: # เก็บที่อยู่บรรทัดแรกที่เจอ
87
+ data['address'] = line
88
+ else:
89
+ data['address'] += ' ' + line
90
+
91
+ # หาวันออกบัตร
92
+ if re.search(r'(ออกบัตร|issued)', line.lower()):
93
+ date_match = re.search(r'(\d{1,2}[\s/.-]\d{1,2}[\s/.-]\d{4})', line)
94
+ if date_match:
95
+ data['issuedate'] = date_match.group()
96
+
97
+ # หาวันหมดอายุ
98
+ if re.search(r'(หมดอายุ|expire)', line.lower()):
99
+ date_match = re.search(r'(\d{1,2}[\s/.-]\d{1,2}[\s/.-]\d{4})', line)
100
+ if date_match:
101
+ data['expiredate'] = date_match.group()
102
+
103
+ except Exception as e:
104
+ print(f"Error parsing Gemini response: {e}")
105
+
106
+ return data
107
+
108
+ # หน้าเริ่มต้น
109
+ @app.route("/", methods=["GET"])
110
+ def index():
111
+ return render_template("index.html")
112
+
113
+ # เมื่อมีการอัปโหลดภาพ
114
+ @app.route("/upload", methods=["POST"])
115
+ def upload():
116
+ try:
117
+ if "image" not in request.files:
118
+ return jsonify({"error": "ไม่พบไฟล์"}), 400
119
+
120
+ file = request.files["image"]
121
+
122
+ if file.filename == '':
123
+ return jsonify({"error": "ไม่ได้เลือกไฟล์"}), 400
124
+
125
+ # ตรวจสอบว่ามีนามสกุลไฟล์หรือไม่
126
+ if '.' not in file.filename:
127
+ return jsonify({"error": "ไฟล์ต้องมีนามสกุล"}), 400
128
+
129
+ file_extension = file.filename.rsplit('.', 1)[1].lower()
130
+
131
+ # ไฟล์ที่อันตรายที่ไม่ควรอนุญาต (blacklist)
132
+ dangerous_extensions = {
133
+ 'exe', 'bat', 'cmd', 'scr', 'vbs', 'jar', 'com', 'pif',
134
+ 'application', 'gadget', 'msi', 'msp', 'hta', 'cpl', 'msc',
135
+ 'wsf', 'wsh', 'ps1', 'ps1xml', 'ps2', 'ps2xml', 'psc1', 'psc2'
136
+ }
137
+
138
+ # ป้องกันไฟล์อันตราย
139
+ if file_extension in dangerous_extensions:
140
+ return jsonify({"error": f"ไฟล์ประเภท .{file_extension} อาจเป็นอันตราย"}), 400
141
+
142
+ # ตรวจสอบขนาดไฟล์ (10MB = 10 * 1024 * 1024 bytes)
143
+ file.seek(0, 2) # ไปที่ท้ายไฟล์
144
+ file_size = file.tell() # ได้ขนาดไฟล์
145
+ file.seek(0) # กลับไปต้นไฟล์
146
+
147
+ if file_size > 10 * 1024 * 1024: # 10MB
148
+ return jsonify({"error": "ไฟล์ใหญ่เกินไป (สูงสุด 10MB)"}), 400
149
+
150
+ # สร้างชื่อไฟล์ใหม่เพื่อป้องกันชื่อซ้ำ
151
+ import time
152
+ timestamp = str(int(time.time()))
153
+ safe_filename = f"{timestamp}_{file.filename}"
154
+ file_path = os.path.join("static", safe_filename)
155
+
156
+ # บันทึกไฟล์
157
+ file.save(file_path)
158
+
159
+ # ตรวจสอบว่าไฟล์เป็นรูปภาพหรือ PDF ก่อนทำ OCR
160
+ image_extensions = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp'}
161
+ pdf_extensions = {'pdf'}
162
+
163
+ raw_text = ""
164
+ refined_text = ""
165
+ data = {}
166
+
167
+ if file_extension in image_extensions or file_extension in pdf_extensions:
168
+ # OCR อ่านข้อความจากภาพหรือ PDF
169
+ try:
170
+ results = reader.readtext(file_path, detail=0)
171
+ raw_text = "\n".join(results)
172
+
173
+ if not raw_text.strip():
174
+ return render_template("index.html",
175
+ error="ไม่สามารถอ่านข้อความจากไฟล์ได้ กรุณาลองใหม่ด้วยไฟล์ที่ชัดขึ้น",
176
+ file_info={"name": file.filename, "size": file_size, "type": file_extension})
177
+
178
+ except Exception as e:
179
+ print(f"OCR Error: {e}")
180
+ return render_template("index.html",
181
+ error="เกิดข้อผิดพลาดในการอ่านไฟล์ กรุณาลองใหม่อีกครั้ง",
182
+ file_info={"name": file.filename, "size": file_size, "type": file_extension})
183
+
184
+ # แก้ไขปัญหาการอ่านเลข 1 เป็น /
185
+ def fix_common_ocr_errors(text):
186
+ # แก้เฉพาะ / ที่น่าจะเป็นเลข 1 เช่น 2/3 → 213
187
+ return re.sub(r'(?<=\d)/(?=\d)', '1', text)
188
+
189
+ raw_text = fix_common_ocr_errors(raw_text)
190
+
191
+ # ส่งข้อความดิบให้ Gemini แก้ไข
192
+ try:
193
+ refined_text = refine_text_with_gemini(raw_text)
194
+ except Exception as e:
195
+ print(f"Gemini Error: {e}")
196
+ refined_text = raw_text # ใช้ข้อความต้นฉบับถ้า Gemini ล้มเหลว
197
+
198
+ # แยกข้อมูลที่สำคัญออกมา
199
+ try:
200
+ data = parse_gemini_response(refined_text)
201
+ except Exception as e:
202
+ print(f"Parse Error: {e}")
203
+ data = {}
204
+ else:
205
+ # ไฟล์ประเภทอื่นที่ไม่ใช่รูปภาพหรือ PDF
206
+ raw_text = f"อัปโหลดไฟล์ {file.filename} สำเร็จ"
207
+ refined_text = f"ไฟล์ประเภท .{file_extension} ได้รับการอัปโหลดเรียบร้อยแล้ว"
208
+ data = {
209
+ "filename": file.filename,
210
+ "file_type": file_extension,
211
+ "file_size": f"{file_size / 1024:.2f} KB"
212
+ }
213
+
214
+ # ลบไฟล์ที่อัปโหลดเพื่อประหยัดพื้นที่ (ถ้าไม่ต้องการเก็บไว้)
215
+ try:
216
+ os.remove(file_path)
217
+ except:
218
+ pass
219
+
220
+ # ส่งข้อมูลไปหน้าเว็บ
221
+ return render_template("index.html",
222
+ raw_text=raw_text,
223
+ refined_text=refined_text,
224
+ data=data,
225
+ file_info={
226
+ "name": file.filename,
227
+ "size": f"{file_size / 1024:.2f} KB",
228
+ "type": file_extension
229
+ })
230
+
231
+ except Exception as e:
232
+ print(f"Unexpected error: {e}")
233
+ return render_template("index.html",
234
+ error="เกิดข้อผิดพลาดที่ไม่คาดคิด กรุณาลองใหม่อีกครั้ง")
235
+
236
+
237
+ @app.route("/api/lucky-draw", methods=["POST"])
238
+ def lucky_draw():
239
+ try:
240
+ if "image" not in request.files:
241
+ return jsonify({"success": False, "message": "ไม่พบไฟล์"}), 400
242
+
243
+ file = request.files["image"]
244
+
245
+ if file.filename == '':
246
+ return jsonify({"success": False, "message": "ไม่ได้เลือกไฟล์"}), 400
247
+
248
+ if '.' not in file.filename:
249
+ return jsonify({"success": False, "message": "ไฟล์ต้องมีนามสกุล"}), 400
250
+
251
+ file_extension = file.filename.rsplit('.', 1)[1].lower()
252
+ allowed_extensions = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp', 'pdf'}
253
+ if file_extension not in allowed_extensions:
254
+ return jsonify({"success": False, "message": "รองรับเฉพาะไฟล์รูปภาพและ PDF เท่านั้น"}), 400
255
+
256
+ file.seek(0, 2)
257
+ file_size = file.tell()
258
+ file.seek(0)
259
+ if file_size > 10 * 1024 * 1024:
260
+ return jsonify({"success": False, "message": "ไฟล์ใหญ่เกินไป (สูงสุด 10MB)"}), 400
261
+
262
+ # บันทึกไฟล์ชั่วคราว
263
+ import time
264
+ timestamp = str(int(time.time()))
265
+ safe_filename = f"{timestamp}_{file.filename}"
266
+ file_path = os.path.join("static", safe_filename)
267
+ file.save(file_path)
268
+
269
+ # OCR อ่านข้อความ
270
+ results = []
271
+ try:
272
+ if file_extension == 'pdf':
273
+ # ถ้า PDF อาจต้องแปลงเป็นรูปก่อน (ง่ายๆ ใช้ pdf2image หรือ library อื่น)
274
+ # แต่ถ้าอยากง่าย ให้บอกว่ารองรับเฉพาะรูปภาพก่อน
275
+ return jsonify({"success": False, "message": "ยังไม่รองรับ PDF สำหรับฟังก์ชันสุ่มชื่อ"}), 400
276
+ else:
277
+ results = reader.readtext(file_path, detail=0)
278
+ except Exception as e:
279
+ print(f"OCR Error: {e}")
280
+ return jsonify({"success": False, "message": "เกิดข้อผิดพลาดในการอ่านไฟล์"}), 500
281
+
282
+ raw_text = "\n".join(results)
283
+
284
+ # ฟังก์ชันช่วยแก้ปัญหาการอ่านเลข 1 ผิดเป็น /
285
+ def fix_common_ocr_errors(text):
286
+ return re.sub(r'(?<=\d)/(?=\d)', '1', text)
287
+ raw_text = fix_common_ocr_errors(raw_text)
288
+
289
+ # สร้าง prompt สำหรับให้ Gemini สุ่มชื่อผู้โชคดี 3 ราย
290
+ prompt = f"""
291
+ ข้อความด้านล่างเป็นรายชื่อผู้เข้าร่วมกิจกรรม (อาจมีหลายบรรทัด):
292
+ {raw_text}
293
+
294
+ กรุณาสุ่มเลือกชื่อผู้โชคดีจำนวน 3 คน และตอบกลับเป็นรายการชื่อแยกบรรทัดใหม่โดยไม่ต้องมีข้อความอื่น
295
+ """
296
+
297
+ # เรียก Gemini เพื่อสุ่มชื่อ
298
+ response = model.generate_content(prompt)
299
+ lucky_names_text = response.text.strip()
300
+
301
+ # แปลงข้อความชื่อผู้โชคดีเป็น list
302
+ lucky_names = [line.strip() for line in lucky_names_text.split('\n') if line.strip()]
303
+
304
+ # ลบไฟล์หลังใช้งาน
305
+ try:
306
+ os.remove(file_path)
307
+ except:
308
+ pass
309
+
310
+ return jsonify({
311
+ "success": True,
312
+ "lucky_names": lucky_names,
313
+ "raw_text": raw_text
314
+ })
315
+
316
+ except Exception as e:
317
+ print(f"Unexpected error in lucky_draw: {e}")
318
+ return jsonify({"success": False, "message": "เกิดข้อผิดพลาดที่ไม่คาดคิด"}), 500
319
+
320
+
321
+ # ===== ทางเลือกอื่น: รองรับทุกไฟล์โดยไม่มีข้อจำกัด =====
322
+
323
+ @app.route("/upload_no_limit", methods=["POST"])
324
+ def upload_no_limit():
325
+ """เวอร์ชันที่รองรับทุกประเภทไฟล์โดยไม่มีข้อจำกัด"""
326
+ try:
327
+ if "image" not in request.files:
328
+ return jsonify({"error": "ไม่พบไฟล์"}), 400
329
+
330
+ file = request.files["image"]
331
+
332
+ if file.filename == '':
333
+ return jsonify({"error": "ไม่ได้เลือกไฟล์"}), 400
334
+
335
+ # ตรวจสอบขนาดไฟล์เท่านั้น (50MB)
336
+ file.seek(0, 2)
337
+ file_size = file.tell()
338
+ file.seek(0)
339
+
340
+ if file_size > 50 * 1024 * 1024: # 50MB
341
+ return jsonify({"error": "ไฟล์ใหญ่เกินไป (สูงสุด 50MB)"}), 400
342
+
343
+ # สร้างชื่อไฟล์ใหม่
344
+ import time
345
+ timestamp = str(int(time.time()))
346
+ safe_filename = f"{timestamp}_{file.filename}"
347
+ file_path = os.path.join("static", safe_filename)
348
+
349
+ # บันทึกไฟล์
350
+ file.save(file_path)
351
+
352
+ # ได้นามสกุลไฟล์ (ถ้ามี)
353
+ file_extension = ""
354
+ if '.' in file.filename:
355
+ file_extension = file.filename.rsplit('.', 1)[1].lower()
356
+
357
+ # ลบไฟล์หลังอัปโหลด
358
+ try:
359
+ os.remove(file_path)
360
+ except:
361
+ pass
362
+
363
+ # ส่งข้อมูลกลับ
364
+ return render_template("index.html",
365
+ raw_text=f"อัปโหลด {file.filename} สำเร็จ",
366
+ refined_text=f"ไฟล์ {file.filename} ได้รับการอัปโหลดเรียบร้อย",
367
+ data={
368
+ "filename": file.filename,
369
+ "file_type": file_extension if file_extension else "ไม่มีนามสกุล",
370
+ "file_size": f"{file_size / 1024:.2f} KB"
371
+ },
372
+ file_info={
373
+ "name": file.filename,
374
+ "size": f"{file_size / 1024:.2f} KB",
375
+ "type": file_extension if file_extension else "unknown"
376
+ })
377
+
378
+ except Exception as e:
379
+ print(f"Unexpected error: {e}")
380
+ return render_template("index.html",
381
+ error="เกิดข้อผิดพลาดที่ไม่คาดคิด กรุณาลองใหม่อีกครั้ง")
382
+
383
+ # API สำหรับบันทึกข้อมูล
384
+ @app.route("/save", methods=["POST"])
385
+ def save_data():
386
+ try:
387
+ data = request.get_json()
388
+
389
+ # ที่นี่คุณสามารถบันทึกข้อมูลลงฐานข้อมูลได้
390
+ # เช่น บันทึกลง SQLite, MySQL, MongoDB เป็นต้น
391
+
392
+ print("Saving data:", data)
393
+
394
+ return jsonify({"success": True, "message": "บันทึกข้อมูลสำเร็จ"})
395
+
396
+ except Exception as e:
397
+ print(f"Save error: {e}")
398
+ return jsonify({"success": False, "error": "ไม่สามารถบันทึกข้อมูลได้"}), 500
399
+
400
+ # Error handlers
401
+ @app.errorhandler(404)
402
+ def not_found(error):
403
+ return render_template("index.html", error="ไม่พบหน้าที่ต้องการ"), 404
404
+
405
+ @app.errorhandler(500)
406
+ def internal_error(error):
407
+ return render_template("index.html", error="เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์"), 500
408
+
409
+ @app.errorhandler(413)
410
+ def too_large(error):
411
+ return render_template("index.html", error="ไฟล์ใหญ่เกินไป (สูงสุด 10MB)"), 413
412
+
413
+ # รัน Flask app
414
+ if __name__ == "__main__":
415
+ app.run(debug=True, host="0.0.0.0", port=5000)
static/117379fb-bd5b-443b-a539-90b29eb8007b.webp ADDED
static/1752755344481.jpg ADDED

Git LFS Details

  • SHA256: cfdabcdb44faa9306aafcf3232b8f8ce03a8ab2d4767b18228a0c8feb8cb7e11
  • Pointer size: 131 Bytes
  • Size of remote file: 796 kB
static/1753071647_ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
static/1753072175_ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
static/1753072439_ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
static/1753072499_1752755344481.jpg ADDED

Git LFS Details

  • SHA256: cfdabcdb44faa9306aafcf3232b8f8ce03a8ab2d4767b18228a0c8feb8cb7e11
  • Pointer size: 131 Bytes
  • Size of remote file: 796 kB
static/1753072773_ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
static/1753086977_ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
static/1753088075_1752755344481.jpg ADDED

Git LFS Details

  • SHA256: cfdabcdb44faa9306aafcf3232b8f8ce03a8ab2d4767b18228a0c8feb8cb7e11
  • Pointer size: 131 Bytes
  • Size of remote file: 796 kB
static/ThaiIDCard_Mr._Sample500x500_processed.jpg ADDED

Git LFS Details

  • SHA256: aa9e6b9b651dd9be02041907ff0839d127fd3db3139e172fe227c643bf8e9b63
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
static/card2.webp ADDED

Git LFS Details

  • SHA256: c29a35f2260ac2673817b39cdd3fca19c1af98825d52ede6414c012e4744dba0
  • Pointer size: 131 Bytes
  • Size of remote file: 253 kB
static/maxresdefault.jpg ADDED

Git LFS Details

  • SHA256: 4a548ee3cb364b396801744b2090632937f735fa1c68e84c549fb9f1d8ab89f3
  • Pointer size: 131 Bytes
  • Size of remote file: 128 kB
static/maxresdefault_processed.jpg ADDED

Git LFS Details

  • SHA256: 5d9e6e62d9c0bae3010604da70e3dcff1bde9cdc39c5410367be8a3a15a9e5ca
  • Pointer size: 131 Bytes
  • Size of remote file: 315 kB
static/preprocessed.png ADDED
static/ผลแล็ป.png ADDED

Git LFS Details

  • SHA256: b06f992635f91fb67a3c1dcffbed94313184c27f12511c4c3387a12f57938645
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
templates/index.html ADDED
@@ -0,0 +1,961 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="th">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>OCR Result</title>
6
+ <link
7
+ href="https://fonts.googleapis.com/css2?family=Kanit&display=swap"
8
+ rel="stylesheet"
9
+ />
10
+
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ font-family: "Kanit", sans-serif;
20
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
21
+ min-height: 100vh;
22
+ padding: 20px;
23
+ color: #333;
24
+ }
25
+
26
+ .container {
27
+ max-width: 1000px;
28
+ margin: 0 auto;
29
+ background: rgba(255, 255, 255, 0.95);
30
+ border-radius: 20px;
31
+ padding: 40px;
32
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
33
+ backdrop-filter: blur(10px);
34
+ }
35
+
36
+ h1 {
37
+ text-align: center;
38
+ color: #2c3e50;
39
+ margin-bottom: 30px;
40
+ font-size: 2.5em;
41
+ font-weight: 300;
42
+ }
43
+
44
+ .upload-section {
45
+ background: white;
46
+ border-radius: 15px;
47
+ padding: 30px;
48
+ margin-bottom: 30px;
49
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
50
+ border: 2px dashed #e1e8ed;
51
+ transition: all 0.3s ease;
52
+ }
53
+
54
+ .upload-section:hover {
55
+ border-color: #667eea;
56
+ transform: translateY(-2px);
57
+ }
58
+
59
+ .upload-area {
60
+ text-align: center;
61
+ padding: 40px 20px;
62
+ cursor: pointer;
63
+ position: relative;
64
+ overflow: hidden;
65
+ }
66
+
67
+ .upload-area::before {
68
+ content: "";
69
+ position: absolute;
70
+ top: 0;
71
+ left: -100%;
72
+ width: 100%;
73
+ height: 100%;
74
+ background: linear-gradient(
75
+ 90deg,
76
+ transparent,
77
+ rgba(255, 255, 255, 0.3),
78
+ transparent
79
+ );
80
+ transition: left 0.5s;
81
+ }
82
+
83
+ .upload-area:hover::before {
84
+ left: 100%;
85
+ }
86
+
87
+ .upload-icon {
88
+ font-size: 4em;
89
+ color: #667eea;
90
+ margin-bottom: 20px;
91
+ transition: transform 0.3s ease;
92
+ }
93
+
94
+ .upload-area:hover .upload-icon {
95
+ transform: scale(1.1);
96
+ }
97
+
98
+ .upload-text {
99
+ font-size: 1.2em;
100
+ color: #666;
101
+ margin-bottom: 10px;
102
+ }
103
+
104
+ .upload-subtext {
105
+ font-size: 0.9em;
106
+ color: #999;
107
+ }
108
+
109
+ input[type="file"] {
110
+ display: none;
111
+ }
112
+
113
+ .btn {
114
+ background: linear-gradient(45deg, #667eea, #764ba2);
115
+ color: white;
116
+ border: none;
117
+ padding: 12px 30px;
118
+ border-radius: 25px;
119
+ font-size: 1em;
120
+ cursor: pointer;
121
+ transition: all 0.3s ease;
122
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
123
+ margin-top: 20px;
124
+ font-family: "Kanit", sans-serif;
125
+ }
126
+
127
+ .btn:hover {
128
+ transform: translateY(-2px);
129
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
130
+ }
131
+
132
+ .btn:disabled {
133
+ background: #95a5a6;
134
+ cursor: not-allowed;
135
+ transform: none;
136
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
137
+ }
138
+
139
+ .section {
140
+ background: white;
141
+ border-radius: 15px;
142
+ padding: 30px;
143
+ margin-bottom: 30px;
144
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
145
+ animation: fadeInUp 0.6s ease-out;
146
+ }
147
+
148
+ @keyframes fadeInUp {
149
+ from {
150
+ opacity: 0;
151
+ transform: translateY(30px);
152
+ }
153
+ to {
154
+ opacity: 1;
155
+ transform: translateY(0);
156
+ }
157
+ }
158
+
159
+ .section h2 {
160
+ color: #2c3e50;
161
+ margin-bottom: 25px;
162
+ font-size: 1.8em;
163
+ font-weight: 300;
164
+ }
165
+
166
+ .field-group {
167
+ margin-bottom: 25px;
168
+ }
169
+
170
+ .field-label {
171
+ display: block;
172
+ margin-bottom: 8px;
173
+ font-weight: 500;
174
+ color: #555;
175
+ font-size: 0.95em;
176
+ }
177
+
178
+ .input-container {
179
+ position: relative;
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 10px;
183
+ }
184
+
185
+ .field-input {
186
+ flex: 1;
187
+ padding: 15px 20px;
188
+ border: 2px solid #e1e8ed;
189
+ border-radius: 10px;
190
+ font-size: 1em;
191
+ transition: all 0.3s ease;
192
+ background: #f8f9fa;
193
+ }
194
+
195
+ .field-input:focus {
196
+ border-color: #667eea;
197
+ background: white;
198
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
199
+ outline: none;
200
+ }
201
+
202
+ .field-input:hover {
203
+ border-color: #c1c8d1;
204
+ }
205
+
206
+ .icon-btn {
207
+ padding: 12px;
208
+ border: none;
209
+ border-radius: 8px;
210
+ cursor: pointer;
211
+ font-size: 1.1em;
212
+ transition: all 0.3s ease;
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ min-width: 44px;
217
+ height: 44px;
218
+ }
219
+
220
+ .edit-btn {
221
+ background: #3498db;
222
+ color: white;
223
+ }
224
+
225
+ .edit-btn:hover {
226
+ background: #2980b9;
227
+ transform: scale(1.05);
228
+ }
229
+
230
+ .delete-btn {
231
+ background: #e74c3c;
232
+ color: white;
233
+ }
234
+
235
+ .delete-btn:hover {
236
+ background: #c0392b;
237
+ transform: scale(1.05);
238
+ }
239
+
240
+ .save-btn {
241
+ background: #27ae60;
242
+ color: white;
243
+ }
244
+
245
+ .save-btn:hover {
246
+ background: #229954;
247
+ transform: scale(1.05);
248
+ }
249
+
250
+ .raw-text {
251
+ background: #f8f9fa;
252
+ border: 1px solid #e1e8ed;
253
+ border-radius: 10px;
254
+ padding: 20px;
255
+ font-family: "Courier New", monospace;
256
+ font-size: 0.9em;
257
+ line-height: 1.6;
258
+ color: #555;
259
+ white-space: pre-wrap;
260
+ max-height: 300px;
261
+ overflow-y: auto;
262
+ }
263
+
264
+ .grid-2 {
265
+ display: grid;
266
+ grid-template-columns: 1fr 1fr;
267
+ gap: 20px;
268
+ }
269
+
270
+ .grid-3 {
271
+ display: grid;
272
+ grid-template-columns: 1fr 1fr 1fr;
273
+ gap: 20px;
274
+ }
275
+
276
+ @media (max-width: 768px) {
277
+ .grid-2,
278
+ .grid-3 {
279
+ grid-template-columns: 1fr;
280
+ }
281
+
282
+ .container {
283
+ padding: 20px;
284
+ }
285
+
286
+ h1 {
287
+ font-size: 2em;
288
+ }
289
+ }
290
+
291
+ .readonly {
292
+ background: #f1f3f4;
293
+ color: #666;
294
+ cursor: not-allowed;
295
+ }
296
+
297
+ .editable {
298
+ background: white;
299
+ }
300
+
301
+ .field-group.editing .field-input {
302
+ border-color: #27ae60;
303
+ background: #f0fff4;
304
+ }
305
+
306
+ .tooltip {
307
+ position: relative;
308
+ }
309
+
310
+ .tooltip::after {
311
+ content: attr(data-tooltip);
312
+ position: absolute;
313
+ bottom: 100%;
314
+ left: 50%;
315
+ transform: translateX(-50%);
316
+ background: #333;
317
+ color: white;
318
+ padding: 5px 10px;
319
+ border-radius: 4px;
320
+ font-size: 0.8em;
321
+ white-space: nowrap;
322
+ opacity: 0;
323
+ pointer-events: none;
324
+ transition: opacity 0.3s;
325
+ z-index: 1000;
326
+ }
327
+
328
+ .tooltip:hover::after {
329
+ opacity: 1;
330
+ }
331
+
332
+ /* Loading spinner animation */
333
+ @keyframes spin {
334
+ 0% {
335
+ transform: rotate(0deg);
336
+ }
337
+ 100% {
338
+ transform: rotate(360deg);
339
+ }
340
+ }
341
+
342
+ .loading-spinner {
343
+ border: 3px solid #f3f3f3;
344
+ border-top: 3px solid #667eea;
345
+ border-radius: 50%;
346
+ width: 30px;
347
+ height: 30px;
348
+ animation: spin 1s linear infinite;
349
+ display: inline-block;
350
+ margin-right: 10px;
351
+ }
352
+ </style>
353
+ </head>
354
+ <body>
355
+ <div class="container">
356
+ <h1>OCR</h1>
357
+
358
+ <div class="upload-section">
359
+ <form
360
+ action="/upload"
361
+ method="post"
362
+ enctype="multipart/form-data"
363
+ id="uploadForm"
364
+ >
365
+ <div
366
+ class="upload-area"
367
+ onclick="document.getElementById('fileInput').click()"
368
+ >
369
+ <div class="upload-icon">📤</div>
370
+ <div class="upload-text">คลิกเพื่ออัปโหลดภาพ</div>
371
+ <div class="upload-subtext">
372
+ รองรับไฟล์ JPG, PNG, PDF (ขนาดไม่เกิน 10MB)
373
+ </div>
374
+ <input
375
+ type="file"
376
+ id="fileInput"
377
+ name="image"
378
+ accept="image/*,.pdf"
379
+ />
380
+ </div>
381
+ <div style="text-align: center">
382
+ <button type="submit" class="btn" id="uploadBtn">
383
+ อัปโหลดและประมวลผล
384
+ </button>
385
+ </div>
386
+ </form>
387
+ </div>
388
+
389
+ <div style="text-align: center; margin-bottom: 40px">
390
+ <button class="btn" id="luckyDrawBtn">🎉 สุ่มผู้โชคดี</button>
391
+ </div>
392
+
393
+ {# {% if raw_text %}
394
+ <div class="section">
395
+ <h2>📄 ข้อความดิบจาก OCR</h2>
396
+ <div class="raw-text">{{ raw_text }}</div>
397
+ </div>
398
+ {% endif %} #} {# {% if refined_text %}
399
+ <div class="section">
400
+ <h2>ข้อมูล OCR</h2>
401
+
402
+ <div class="field-group">
403
+ <label class="field-label">ชื่อ-นามสกุล</label>
404
+ <div class="input-container">
405
+ <input
406
+ type="text"
407
+ class="field-input readonly"
408
+ value="{{ data.fullname or '' }}"
409
+ readonly
410
+ id="fullname"
411
+ />
412
+ <button
413
+ class="icon-btn edit-btn tooltip"
414
+ data-tooltip="แก้ไข"
415
+ onclick="toggleEdit('fullname')"
416
+ >
417
+ ✏️
418
+ </button>
419
+ <button
420
+ class="icon-btn delete-btn tooltip"
421
+ data-tooltip="ลบ"
422
+ onclick="clearField('fullname')"
423
+ >
424
+ 🗑️
425
+ </button>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="field-group">
430
+ <label class="field-label">เลขประจำตัวประชาชน</label>
431
+ <div class="input-container">
432
+ <input
433
+ type="text"
434
+ class="field-input readonly"
435
+ value="{{ data.idnumber or '' }}"
436
+ readonly
437
+ id="idnumber"
438
+ />
439
+ <button
440
+ class="icon-btn edit-btn tooltip"
441
+ data-tooltip="แก้ไข"
442
+ onclick="toggleEdit('idnumber')"
443
+ >
444
+ ✏️
445
+ </button>
446
+ <button
447
+ class="icon-btn delete-btn tooltip"
448
+ data-tooltip="ลบ"
449
+ onclick="clearField('idnumber')"
450
+ >
451
+ 🗑️
452
+ </button>
453
+ </div>
454
+ </div>
455
+
456
+ <div class="grid-2">
457
+ <div class="field-group">
458
+ <label class="field-label">วัน/เดือน/ปีเกิด</label>
459
+ <div class="input-container">
460
+ <input
461
+ type="text"
462
+ class="field-input readonly"
463
+ value="{{ data.birthdate or '' }}"
464
+ readonly
465
+ id="birthdate"
466
+ />
467
+ <button
468
+ class="icon-btn edit-btn tooltip"
469
+ data-tooltip="แก้ไข"
470
+ onclick="toggleEdit('birthdate')"
471
+ >
472
+ ✏️
473
+ </button>
474
+ <button
475
+ class="icon-btn delete-btn tooltip"
476
+ data-tooltip="ลบ"
477
+ onclick="clearField('birthdate')"
478
+ >
479
+ 🗑️
480
+ </button>
481
+ </div>
482
+ </div>
483
+
484
+ <div class="field-group">
485
+ <label class="field-label">ศาสนา</label>
486
+ <div class="input-container">
487
+ <input
488
+ type="text"
489
+ class="field-input readonly"
490
+ value="{{ data.religion or '' }}"
491
+ readonly
492
+ id="religion"
493
+ />
494
+ <button
495
+ class="icon-btn edit-btn tooltip"
496
+ data-tooltip="แก้ไข"
497
+ onclick="toggleEdit('religion')"
498
+ >
499
+ ✏️
500
+ </button>
501
+ <button
502
+ class="icon-btn delete-btn tooltip"
503
+ data-tooltip="ลบ"
504
+ onclick="clearField('religion')"
505
+ >
506
+ 🗑️
507
+ </button>
508
+ </div>
509
+ </div>
510
+ </div>
511
+
512
+ <div class="field-group">
513
+ <label class="field-label">ที่อยู่</label>
514
+ <div class="input-container">
515
+ <input
516
+ type="text"
517
+ class="field-input readonly"
518
+ value="{{ data.address or '' }}"
519
+ readonly
520
+ id="address"
521
+ />
522
+ <button
523
+ class="icon-btn edit-btn tooltip"
524
+ data-tooltip="แก้ไข"
525
+ onclick="toggleEdit('address')"
526
+ >
527
+ ✏️
528
+ </button>
529
+ <button
530
+ class="icon-btn delete-btn tooltip"
531
+ data-tooltip="ลบ"
532
+ onclick="clearField('address')"
533
+ >
534
+ 🗑️
535
+ </button>
536
+ </div>
537
+ </div>
538
+
539
+ <div class="grid-2">
540
+ <div class="field-group">
541
+ <label class="field-label">วันออกบัตร</label>
542
+ <div class="input-container">
543
+ <input
544
+ type="text"
545
+ class="field-input readonly"
546
+ value="{{ data.issuedate or '' }}"
547
+ readonly
548
+ id="issuedate"
549
+ />
550
+ <button
551
+ class="icon-btn edit-btn tooltip"
552
+ data-tooltip="แก้ไข"
553
+ onclick="toggleEdit('issuedate')"
554
+ >
555
+ ✏️
556
+ </button>
557
+ <button
558
+ class="icon-btn delete-btn tooltip"
559
+ data-tooltip="ลบ"
560
+ onclick="clearField('issuedate')"
561
+ >
562
+ 🗑️
563
+ </button>
564
+ </div>
565
+ </div>
566
+
567
+ <div class="field-group">
568
+ <label class="field-label">วันหมดอายุ</label>
569
+ <div class="input-container">
570
+ <input
571
+ type="text"
572
+ class="field-input readonly"
573
+ value="{{ data.expiredate or '' }}"
574
+ readonly
575
+ id="expiredate"
576
+ />
577
+ <button
578
+ class="icon-btn edit-btn tooltip"
579
+ data-tooltip="แก้ไข"
580
+ onclick="toggleEdit('expiredate')"
581
+ >
582
+ ✏️
583
+ </button>
584
+ <button
585
+ class="icon-btn delete-btn tooltip"
586
+ data-tooltip="ลบ"
587
+ onclick="clearField('expiredate')"
588
+ >
589
+ 🗑️
590
+ </button>
591
+ </div>
592
+ </div>
593
+ </div>
594
+
595
+ <div style="text-align: center; margin-top: 30px">
596
+ <button class="btn" onclick="saveAllData()">💾 บันทึกข้อมูล</button>
597
+ </div>
598
+ </div>
599
+ {% endif %}
600
+ </div>
601
+ #} {% if refined_text %}
602
+ <div class="section">
603
+ <h2>ข้อความที่ผ่านการ OCR</h2>
604
+ <textarea
605
+ rows="10"
606
+ style="
607
+ width: 100%;
608
+ font-size: 1.1em;
609
+ padding: 15px;
610
+ border-radius: 10px;
611
+ border: 1px solid #ccc;
612
+ "
613
+ readonly
614
+ >
615
+ {{ refined_text }}</textarea
616
+ >
617
+ </div>
618
+ {% endif %}
619
+
620
+ <!-- SweetAlert2 -->
621
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
622
+ <script>
623
+ document.getElementById("luckyDrawBtn").addEventListener("click", () => {
624
+ Swal.fire({
625
+ title: "สุ่มผู้โชคดี - อัปโหลดรูป",
626
+ html: `
627
+ <input type="file" id="luckyFileInput" accept="image/*,.pdf" style="display:none" />
628
+ <div class="upload-area" id="luckyUploadArea" style="cursor:pointer; padding:30px; border: 2px dashed #667eea; border-radius: 15px;">
629
+ <div style="font-size: 4em; color: #667eea; margin-bottom: 20px;">📤</div>
630
+ <div id="luckyUploadText" style="font-size: 1.2em; color: #666;">คลิกเพื่อเลือกไฟล์</div>
631
+ <div style="font-size: 0.9em; color: #999;">รองรับไฟล์ JPG, PNG, PDF (ไม่เกิน 10MB)</div>
632
+ </div>
633
+ `,
634
+ showCancelButton: true,
635
+ confirmButtonText: "สุ่มชื่อ",
636
+ cancelButtonText: "ยกเลิก",
637
+ preConfirm: () => {
638
+ const fileInput = document.getElementById("luckyFileInput");
639
+ if (!fileInput.files || fileInput.files.length === 0) {
640
+ Swal.showValidationMessage("กรุณาเลือกไฟล์ก่อน");
641
+ return false;
642
+ }
643
+ return fileInput.files[0];
644
+ },
645
+ didOpen: () => {
646
+ const uploadArea = document.getElementById("luckyUploadArea");
647
+ const fileInput = document.getElementById("luckyFileInput");
648
+ const uploadText = document.getElementById("luckyUploadText");
649
+
650
+ uploadArea.addEventListener("click", () => fileInput.click());
651
+ fileInput.addEventListener("change", (e) => {
652
+ if (fileInput.files.length > 0) {
653
+ uploadText.textContent = `เลือกไฟล์: ${fileInput.files[0].name}`;
654
+ uploadText.style.color = "#27ae60";
655
+ }
656
+ });
657
+ },
658
+ }).then((result) => {
659
+ if (result.isConfirmed) {
660
+ // เริ่มอัปโหลดและสุ่มชื่อ
661
+ const file = result.value;
662
+ const formData = new FormData();
663
+ formData.append("image", file);
664
+
665
+ Swal.fire({
666
+ title: "กำลังประมวลผลและสุ่มชื่อผู้โชคดี...",
667
+ html: '<div class="loading-spinner"></div>',
668
+ allowOutsideClick: false,
669
+ showConfirmButton: false,
670
+ });
671
+
672
+ fetch("/api/lucky-draw", {
673
+ method: "POST",
674
+ body: formData,
675
+ })
676
+ .then((res) => res.json())
677
+ .then((data) => {
678
+ if (data.success) {
679
+ Swal.fire({
680
+ title: "รายชื่อผู้โชคดี 3 ราย",
681
+ html: `<ol style="text-align: left; font-size:1.2em;">
682
+ ${data.lucky_names.map((name) => `<li>${name}</li>`).join("")}
683
+ </ol>`,
684
+ icon: "success",
685
+ confirmButtonText: "ปิด",
686
+ });
687
+ } else {
688
+ Swal.fire({
689
+ icon: "error",
690
+ title: "เกิดข้อผิดพลาด",
691
+ text: data.message || "ไม่สามารถสุ่มชื่อได้ กรุณาลองใหม่",
692
+ });
693
+ }
694
+ })
695
+ .catch(() => {
696
+ Swal.fire({
697
+ icon: "error",
698
+ title: "เกิดข้อผิดพลาด",
699
+ text: "ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้ กรุณาลองใหม่",
700
+ });
701
+ });
702
+ }
703
+ });
704
+ });
705
+
706
+ // Variable to track loading state
707
+ let isProcessing = false;
708
+
709
+ document
710
+ .getElementById("uploadForm")
711
+ .addEventListener("submit", function (e) {
712
+ const fileInput = document.getElementById("fileInput");
713
+ const uploadBtn = document.getElementById("uploadBtn");
714
+
715
+ // Check if file is selected
716
+ if (!fileInput.files || fileInput.files.length === 0) {
717
+ Swal.fire({
718
+ icon: "warning",
719
+ title: "กรุณาเลือกไฟล์ก่อนอัปโหลด",
720
+ confirmButtonText: "ตกลง",
721
+ });
722
+ e.preventDefault();
723
+ return false;
724
+ }
725
+
726
+ // Prevent multiple submissions
727
+ if (isProcessing) {
728
+ e.preventDefault();
729
+ return false;
730
+ }
731
+
732
+ isProcessing = true;
733
+
734
+ // Disable the upload button
735
+ uploadBtn.disabled = true;
736
+ uploadBtn.textContent = "กำลังประมวลผล...";
737
+
738
+ // Show loading alert with progress
739
+ let progressValue = 0;
740
+ const loadingAlert = Swal.fire({
741
+ title: "กำลังประมวลผลภาพ",
742
+ html: `
743
+ <div style="text-align: center; padding: 20px 0;">
744
+ <div class="loading-spinner"></div>
745
+ <p style="margin-top: 15px; color: #666;">
746
+ กำลังอ่านข้อความจากภาพด้วย OCR และ AI...
747
+ </p>
748
+ <div style="margin-top: 20px;">
749
+ <div style="background: #f0f0f0; border-radius: 10px; height: 8px; overflow: hidden;">
750
+ <div id="progress-bar" style="background: linear-gradient(45deg, #667eea, #764ba2); height: 100%; width: 0%; transition: width 0.3s ease;"></div>
751
+ </div>
752
+ <p id="progress-text" style="margin-top: 10px; font-size: 0.9em; color: #888;">เริ่มต้นการประมวลผล...</p>
753
+ </div>
754
+ </div>
755
+ `,
756
+ allowOutsideClick: false,
757
+ allowEscapeKey: false,
758
+ showConfirmButton: false,
759
+ didOpen: () => {
760
+ // Simulate progress updates
761
+ const progressBar = document.getElementById("progress-bar");
762
+ const progressText = document.getElementById("progress-text");
763
+
764
+ const progressSteps = [
765
+ { percent: 20, text: "กำลังอัปโหลดไฟล์..." },
766
+ { percent: 40, text: "กำลังประมวลผลภาพ..." },
767
+ { percent: 60, text: "กำลังอ่านข้อความด้วย OCR..." },
768
+ { percent: 80, text: "กำลังปรับปรุงข้อความด้วย AI..." },
769
+ { percent: 95, text: "เกือบเสร็จแล้ว..." },
770
+ ];
771
+
772
+ let currentStep = 0;
773
+ const updateProgress = () => {
774
+ if (currentStep < progressSteps.length) {
775
+ const step = progressSteps[currentStep];
776
+ progressBar.style.width = step.percent + "%";
777
+ progressText.textContent = step.text;
778
+ currentStep++;
779
+ setTimeout(updateProgress, 1000 + Math.random() * 1000);
780
+ }
781
+ };
782
+
783
+ setTimeout(updateProgress, 500);
784
+ },
785
+ });
786
+
787
+ // Handle form submission errors
788
+ const originalSubmit = this.submit;
789
+ this.addEventListener("error", function () {
790
+ isProcessing = false;
791
+ uploadBtn.disabled = false;
792
+ uploadBtn.textContent = "อัปโหลดและประมวลผล";
793
+ Swal.close();
794
+ Swal.fire({
795
+ icon: "error",
796
+ title: "เกิดข้อผิดพลาด",
797
+ text: "ไม่สามารถประมวลผลไฟล์ได้ กรุณาลองใหม่อีกครั้ง",
798
+ confirmButtonText: "ตกลง",
799
+ });
800
+ });
801
+ });
802
+
803
+ // Handle page unload to reset state
804
+ window.addEventListener("beforeunload", function () {
805
+ if (isProcessing) {
806
+ isProcessing = false;
807
+ const uploadBtn = document.getElementById("uploadBtn");
808
+ uploadBtn.disabled = false;
809
+ uploadBtn.textContent = "อัปโหลดและประมวลผล";
810
+ }
811
+ });
812
+
813
+ // Toggle edit mode for input fields
814
+ function toggleEdit(fieldId) {
815
+ const input = document.getElementById(fieldId);
816
+ const container = input.closest(".field-group");
817
+ const editBtn = container.querySelector(".edit-btn");
818
+
819
+ if (input.readOnly) {
820
+ // Enter edit mode
821
+ input.readOnly = false;
822
+ input.classList.remove("readonly");
823
+ input.classList.add("editable");
824
+ container.classList.add("editing");
825
+ editBtn.innerHTML = "💾";
826
+ editBtn.classList.remove("edit-btn");
827
+ editBtn.classList.add("save-btn");
828
+ editBtn.setAttribute("data-tooltip", "บันทึก");
829
+ input.focus();
830
+ } else {
831
+ // Save and exit edit mode
832
+ input.readOnly = true;
833
+ input.classList.remove("editable");
834
+ input.classList.add("readonly");
835
+ container.classList.remove("editing");
836
+ editBtn.innerHTML = "✏️";
837
+ editBtn.classList.remove("save-btn");
838
+ editBtn.classList.add("edit-btn");
839
+ editBtn.setAttribute("data-tooltip", "แก้ไข");
840
+
841
+ // Here you could add AJAX call to save data
842
+ showSaveNotification();
843
+ }
844
+ }
845
+
846
+ // Clear field value
847
+ function clearField(fieldId) {
848
+ Swal.fire({
849
+ title: "คุณแน่ใจหรือไม่?",
850
+ text: "ต้องการลบข้อมูลนี้?",
851
+ icon: "warning",
852
+ showCancelButton: true,
853
+ confirmButtonColor: "#e74c3c",
854
+ cancelButtonColor: "#95a5a6",
855
+ confirmButtonText: "ใช่, ลบเลย!",
856
+ cancelButtonText: "ยกเลิก",
857
+ }).then((result) => {
858
+ if (result.isConfirmed) {
859
+ const input = document.getElementById(fieldId);
860
+ input.value = "";
861
+ input.focus();
862
+ Swal.fire({
863
+ title: "ลบแล้ว!",
864
+ text: "ข้อมูลได้ถูกลบเรียบร้อยแล้ว",
865
+ icon: "success",
866
+ timer: 1500,
867
+ showConfirmButton: false,
868
+ });
869
+ }
870
+ });
871
+ }
872
+
873
+ // Save all data
874
+ function saveAllData() {
875
+ const formData = {
876
+ fullname: document.getElementById("fullname").value,
877
+ idnumber: document.getElementById("idnumber").value,
878
+ birthdate: document.getElementById("birthdate").value,
879
+ religion: document.getElementById("religion").value,
880
+ address: document.getElementById("address").value,
881
+ issuedate: document.getElementById("issuedate").value,
882
+ expiredate: document.getElementById("expiredate").value,
883
+ };
884
+
885
+ // Show saving progress
886
+ Swal.fire({
887
+ title: "กำลังบันทึกข้อมูล...",
888
+ html: '<div class="loading-spinner"></div>',
889
+ allowOutsideClick: false,
890
+ showConfirmButton: false,
891
+ timer: 1500,
892
+ }).then(() => {
893
+ // Success message
894
+ Swal.fire({
895
+ icon: "success",
896
+ title: "บันทึกสำเร็จ!",
897
+ text: "ข้อมูลได้ถูกบันทึกเรียบร้อยแล้ว",
898
+ timer: 2000,
899
+ showConfirmButton: false,
900
+ });
901
+ });
902
+
903
+ console.log("Saving data:", formData);
904
+
905
+ // Here you would typically send data to server
906
+ // fetch('/save', { method: 'POST', body: JSON.stringify(formData) })
907
+ }
908
+
909
+ // Show save notification (for individual field edits)
910
+ function showSaveNotification() {
911
+ const notification = document.createElement("div");
912
+ notification.style.cssText = `
913
+ position: fixed;
914
+ top: 20px;
915
+ right: 20px;
916
+ background: #27ae60;
917
+ color: white;
918
+ padding: 15px 20px;
919
+ border-radius: 8px;
920
+ z-index: 1000;
921
+ font-weight: 500;
922
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
923
+ animation: slideIn 0.3s ease-out;
924
+ `;
925
+ notification.innerHTML = "✅ บันทึกข้อมูลเรียบร้อยแล้ว";
926
+ document.body.appendChild(notification);
927
+
928
+ setTimeout(() => {
929
+ notification.style.animation = "slideOut 0.3s ease-out";
930
+ setTimeout(() => notification.remove(), 300);
931
+ }, 2000);
932
+ }
933
+
934
+ // File input change event
935
+ document
936
+ .getElementById("fileInput")
937
+ .addEventListener("change", function (e) {
938
+ const file = e.target.files[0];
939
+ if (file) {
940
+ const uploadText = document.querySelector(".upload-text");
941
+ uploadText.textContent = `เลือกไฟล์: ${file.name}`;
942
+ uploadText.style.color = "#27ae60";
943
+ }
944
+ });
945
+
946
+ // Add CSS animations
947
+ const style = document.createElement("style");
948
+ style.textContent = `
949
+ @keyframes slideIn {
950
+ from { transform: translateX(100%); opacity: 0; }
951
+ to { transform: translateX(0); opacity: 1; }
952
+ }
953
+ @keyframes slideOut {
954
+ from { transform: translateX(0); opacity: 1; }
955
+ to { transform: translateX(100%); opacity: 0; }
956
+ }
957
+ `;
958
+ document.head.appendChild(style);
959
+ </script>
960
+ </body>
961
+ </html>