Spaces:
Runtime error
Runtime error
| # ============================================================ | |
| # OCR + Email ๅไฝตๅทฅๅ ท๏ผGemini OCR โ Excel โ Resend ๅฏไฟก๏ผ | |
| # ============================================================ | |
| # ๅฎ่ฃไพ่ณด๏ผpip install resend google-genai openpyxl ipywidgets | |
| # ============================================================ | |
| import os | |
| import re | |
| import base64 | |
| import resend | |
| import openpyxl | |
| from openpyxl.styles import Font, Alignment, PatternFill | |
| from google import genai | |
| from google.genai import types | |
| import ipywidgets as widgets | |
| from IPython.display import display, clear_output, HTML | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # UI ๅ ไปถ๏ผAPI ้้ฐ & ่จญๅฎ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| style = {'description_width': '140px'} | |
| layout = widgets.Layout(width='600px') | |
| gemini_key_input = widgets.Password( | |
| description='Gemini API Key:', | |
| placeholder='่ฒผไธไฝ ็ Gemini API Key', | |
| style=style, layout=layout | |
| ) | |
| resend_key_input = widgets.Password( | |
| description='Resend API Key:', | |
| placeholder='่ฒผไธไฝ ็ Resend API Key', | |
| style=style, layout=layout | |
| ) | |
| sender_input = widgets.Text( | |
| description='ๅฏไปถไบบ:', | |
| value='Acme <onboarding@resend.dev>', | |
| style=style, layout=layout | |
| ) | |
| recipient_input = widgets.Text( | |
| description='ๆถไปถไบบ Email:', | |
| placeholder='example@gmail.com', | |
| style=style, layout=layout | |
| ) | |
| subject_input = widgets.Text( | |
| description='้ตไปถไธปๆจ:', | |
| value='OCR ่พจ่ญ็ตๆ', | |
| style=style, layout=layout | |
| ) | |
| prompt_input = widgets.Text( | |
| description='OCR Prompt:', | |
| value='ๅนซๆ้ฒ่กOCR๏ผๆๅๅ็ไธญ็ๆๆๆๅญ๏ผไฟ็ๅๅงๆ็็ตๆง', | |
| style=style, layout=layout | |
| ) | |
| upload = widgets.FileUpload(accept='image/*', multiple=True) | |
| button = widgets.Button( | |
| description='๐ ้ๅง่พจ่ญไธฆๅฏไฟก', | |
| button_style='primary', | |
| layout=widgets.Layout(width='220px') | |
| ) | |
| output = widgets.Output() | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๅทฅๅ ทๅฝๅผ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def sanitize_sheet_name(name: str, index: int) -> str: | |
| name = re.sub(r'[\\/*?:\[\]]', '_', name) | |
| return name[:31] if len(name) <= 31 else f"ๅ็_{index+1}" | |
| def build_excel(files: list, prompt: str, client) -> tuple[bytes, str]: | |
| """ๅท่ก OCR ไธฆๅปบ็ซ Excel๏ผๅๅณ (bytes, ๆ่ฆๆๅญ)""" | |
| output_path = 'ocr_results.xlsx' | |
| wb = openpyxl.Workbook() | |
| wb.remove(wb.active) | |
| header_font = Font(name='Arial', bold=True, size=12, color='FFFFFF') | |
| header_fill = PatternFill('solid', start_color='2F5496') | |
| header_align = Alignment(horizontal='center', vertical='center') | |
| body_font = Font(name='Arial', size=11) | |
| label_font = Font(name='Arial', bold=True, size=11, color='2F5496') | |
| meta_fill = PatternFill('solid', start_color='D9E1F2') | |
| summary_lines = [] | |
| for idx, (filename, file_info) in enumerate(files): | |
| print(f'[{idx+1}/{len(files)}] ่พจ่ญ๏ผ{filename} ...') | |
| img_bytes = bytes(file_info['content']) | |
| mime_type = file_info['metadata']['type'] | |
| try: | |
| response = client.models.generate_content( | |
| model='gemini-2.5-flash', | |
| contents=[ | |
| types.Part.from_bytes(data=img_bytes, mime_type=mime_type), | |
| prompt | |
| ] | |
| ) | |
| ocr_text = response.text | |
| except Exception as e: | |
| ocr_text = f'โ ่พจ่ญๅคฑๆ๏ผ{e}' | |
| sheet_name = sanitize_sheet_name(filename, idx) | |
| ws = wb.create_sheet(title=sheet_name) | |
| ws.column_dimensions['A'].width = 10 | |
| ws.column_dimensions['B'].width = 100 | |
| ws['A1'] = '่ก่' | |
| ws['B1'] = 'ๆๅญๅ งๅฎน' | |
| for cell in [ws['A1'], ws['B1']]: | |
| cell.font = header_font | |
| cell.fill = header_fill | |
| cell.alignment = header_align | |
| ws.row_dimensions[1].height = 22 | |
| for label, value in [('ๆชๆกๅ็จฑ', filename), ('MIME ้กๅ', mime_type)]: | |
| ws.append(['', f'ใ{label}ใ{value}']) | |
| row_idx = ws.max_row | |
| cell_b = ws.cell(row=row_idx, column=2) | |
| cell_b.font = label_font | |
| cell_b.fill = meta_fill | |
| cell_b.alignment = Alignment(vertical='center') | |
| ws.row_dimensions[row_idx].height = 18 | |
| ws.append(['', '']) | |
| lines = ocr_text.splitlines() | |
| for line_no, line in enumerate(lines, start=1): | |
| ws.append([line_no, line]) | |
| row_idx = ws.max_row | |
| ws.cell(row=row_idx, column=1).font = Font(name='Arial', size=10, color='888888') | |
| ws.cell(row=row_idx, column=1).alignment = Alignment(horizontal='center', vertical='center') | |
| ws.cell(row=row_idx, column=2).font = body_font | |
| ws.cell(row=row_idx, column=2).alignment = Alignment(vertical='center') | |
| ws.row_dimensions[row_idx].height = 18 | |
| summary_lines.append(f'โข {filename}๏ผๅ ฑ {len(lines)} ่ก') | |
| print(f' โ ๅฎๆ๏ผๅ ฑ {len(lines)} ่ก') | |
| wb.save(output_path) | |
| with open(output_path, 'rb') as f: | |
| excel_bytes = f.read() | |
| return excel_bytes, '\n'.join(summary_lines) | |
| def send_email_with_attachment( | |
| api_key: str, sender: str, recipient: str, | |
| subject: str, excel_bytes: bytes, summary: str | |
| ): | |
| """้้ Resend ๅฏ้ๅซ Excel ้ไปถ็้ตไปถ""" | |
| resend.api_key = api_key | |
| b64_content = base64.b64encode(excel_bytes).decode() | |
| html_body = f""" | |
| <h2>๐ OCR ่พจ่ญ็ตๆ</h2> | |
| <p>ๆจๅฅฝ๏ผไปฅไธๆฏๆฌๆฌก OCR ่พจ่ญๆ่ฆ๏ผ</p> | |
| <pre style="background:#f4f4f4;padding:12px;border-radius:6px;">{summary}</pre> | |
| <p>่ฉณ็ดฐ็ตๆ่ซ่ฆ้ไปถ <strong>ocr_results.xlsx</strong>ใ</p> | |
| <hr> | |
| <small style="color:#888;">็ฑ Gemini OCR ๅทฅๅ ท่ชๅ็ข็</small> | |
| """ | |
| params: resend.Emails.SendParams = { | |
| 'from': sender, | |
| 'to': [recipient], | |
| 'subject': subject, | |
| 'html': html_body, | |
| 'attachments': [ | |
| { | |
| 'filename': 'ocr_results.xlsx', | |
| 'content': b64_content, | |
| } | |
| ], | |
| } | |
| result = resend.Emails.send(params) | |
| return result | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๆ้ไบไปถ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def on_click(b): | |
| with output: | |
| clear_output() | |
| # ้ฉ่ญ่ผธๅ ฅ | |
| if not gemini_key_input.value.strip(): | |
| print('โ ๏ธ ่ซๅกซๅ ฅ Gemini API Key๏ผ') | |
| return | |
| if not resend_key_input.value.strip(): | |
| print('โ ๏ธ ่ซๅกซๅ ฅ Resend API Key๏ผ') | |
| return | |
| if not recipient_input.value.strip(): | |
| print('โ ๏ธ ่ซๅกซๅ ฅๆถไปถไบบ Email๏ผ') | |
| return | |
| if not upload.value: | |
| print('โ ๏ธ ่ซๅ ไธๅณ่ณๅฐไธๅผตๅ็๏ผ') | |
| return | |
| # ๅๅงๅ Gemini client | |
| client = genai.Client(api_key=gemini_key_input.value.strip()) | |
| files = list(upload.value.items()) | |
| print(f'ๅ ฑ {len(files)} ๅผตๅ็๏ผ้ๅง่พจ่ญ...\n') | |
| # OCR + ๅปบ็ซ Excel | |
| try: | |
| excel_bytes, summary = build_excel(files, prompt_input.value, client) | |
| except Exception as e: | |
| print(f'โ OCR ๆ Excel ๅปบ็ซๅคฑๆ๏ผ{e}') | |
| return | |
| # ๆไพไธ่ผ้ฃ็ต | |
| b64 = base64.b64encode(excel_bytes).decode() | |
| link_html = ( | |
| f'<a href="data:application/vnd.openxmlformats-officedocument' | |
| f'.spreadsheetml.sheet;base64,{b64}" download="ocr_results.xlsx">' | |
| f'๐ฅ ้ปๆญคไธ่ผ ocr_results.xlsx</a>' | |
| ) | |
| display(HTML(link_html)) | |
| # ๅฏ้้ตไปถ | |
| print(f'\n๐ง ๆญฃๅจๅฏ้่ณ {recipient_input.value.strip()} ...') | |
| try: | |
| result = send_email_with_attachment( | |
| api_key=resend_key_input.value.strip(), | |
| sender=sender_input.value.strip(), | |
| recipient=recipient_input.value.strip(), | |
| subject=subject_input.value.strip(), | |
| excel_bytes=excel_bytes, | |
| summary=summary | |
| ) | |
| print(f'โ ้ตไปถๅฏ้ๆๅ๏ผID๏ผ{result.get("id", result)}') | |
| except Exception as e: | |
| print(f'โ ้ตไปถๅฏ้ๅคฑๆ๏ผ{e}') | |
| button.on_click(on_click) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ้กฏ็คบไป้ข | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| display( | |
| widgets.HTML('<h3>๐ API ่จญๅฎ</h3>'), | |
| gemini_key_input, | |
| resend_key_input, | |
| widgets.HTML('<h3>๐ง ้ตไปถ่จญๅฎ</h3>'), | |
| sender_input, | |
| recipient_input, | |
| subject_input, | |
| widgets.HTML('<h3>๐ผ๏ธ ๅ็่พจ่ญ่จญๅฎ</h3>'), | |
| prompt_input, | |
| upload, | |
| button, | |
| output | |
| ) |