724 lines
26 KiB
Python
724 lines
26 KiB
Python
"""
|
|
PDF 도면 분석기 - 메인 애플리케이션 (업데이트됨)
|
|
Flet 기반의 PDF 업로드 및 Gemini API 이미지 분석 애플리케이션
|
|
"""
|
|
|
|
import flet as ft
|
|
import logging
|
|
import threading
|
|
from typing import Optional
|
|
import time
|
|
|
|
# 프로젝트 모듈 임포트
|
|
from config import Config
|
|
from pdf_processor import PDFProcessor
|
|
from gemini_analyzer import GeminiAnalyzer
|
|
from ui_components import UIComponents
|
|
from utils import AnalysisResultSaver, DateTimeUtils
|
|
|
|
# 로깅 설정
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class PDFAnalyzerApp:
|
|
"""PDF 분석기 메인 애플리케이션 클래스"""
|
|
|
|
def __init__(self, page: ft.Page):
|
|
self.page = page
|
|
self.pdf_processor = PDFProcessor()
|
|
self.gemini_analyzer = None
|
|
self.current_pdf_path = None
|
|
self.current_pdf_info = None
|
|
self.analysis_results = {}
|
|
self.result_saver = AnalysisResultSaver("results")
|
|
self.analysis_start_time = None
|
|
|
|
# UI 컴포넌트 참조
|
|
self.file_picker = None
|
|
self.selected_file_text = None
|
|
self.upload_button = None
|
|
self.progress_bar = None
|
|
self.progress_ring = None
|
|
self.status_text = None
|
|
self.results_text = None
|
|
self.results_container = None
|
|
self.save_button = None
|
|
self.organization_selector = None # 새로 추가
|
|
self.page_selector = None
|
|
self.analysis_mode = None
|
|
self.custom_prompt = None
|
|
self.pdf_preview_container = None
|
|
self.page_nav_text = None
|
|
self.prev_button = None
|
|
self.next_button = None
|
|
|
|
# 초기화
|
|
self.setup_page()
|
|
self.init_gemini_analyzer()
|
|
|
|
def setup_page(self):
|
|
"""페이지 기본 설정"""
|
|
self.page.title = Config.APP_TITLE
|
|
self.page.theme_mode = ft.ThemeMode.LIGHT
|
|
self.page.padding = 0
|
|
self.page.bgcolor = ft.Colors.GREY_100
|
|
|
|
# 윈도우 크기 설정
|
|
self.page.window_width = 1200
|
|
self.page.window_height = 800
|
|
self.page.window_min_width = 1000
|
|
self.page.window_min_height = 700
|
|
|
|
logger.info("페이지 설정 완료")
|
|
|
|
def init_gemini_analyzer(self):
|
|
"""Gemini 분석기 초기화"""
|
|
try:
|
|
config_errors = Config.validate_config()
|
|
if config_errors:
|
|
self.show_error_dialog(
|
|
"설정 오류",
|
|
"\\n".join(config_errors) + "\\n\\n.env 파일을 확인하세요."
|
|
)
|
|
return
|
|
|
|
self.gemini_analyzer = GeminiAnalyzer()
|
|
logger.info("Gemini 분석기 초기화 완료")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Gemini 분석기 초기화 실패: {e}")
|
|
self.show_error_dialog(
|
|
"초기화 오류",
|
|
f"Gemini API 초기화에 실패했습니다:\\n{str(e)}"
|
|
)
|
|
|
|
def build_ui(self):
|
|
"""UI 구성"""
|
|
|
|
# 앱바
|
|
app_bar = UIComponents.create_app_bar()
|
|
self.page.appbar = app_bar
|
|
|
|
# 파일 업로드 섹션
|
|
upload_section = self.create_file_upload_section()
|
|
|
|
# 분석 설정 섹션
|
|
settings_section = self.create_analysis_settings_section()
|
|
|
|
# 진행률 섹션
|
|
progress_section = self.create_progress_section()
|
|
|
|
# 결과 및 미리보기 섹션
|
|
content_row = ft.Row([
|
|
ft.Column([
|
|
self.create_results_section(),
|
|
], expand=2),
|
|
ft.Column([
|
|
self.create_pdf_preview_section(),
|
|
], expand=1),
|
|
])
|
|
|
|
# 메인 레이아웃
|
|
main_content = ft.Column([
|
|
upload_section,
|
|
settings_section,
|
|
progress_section,
|
|
content_row,
|
|
], scroll=ft.ScrollMode.AUTO)
|
|
|
|
# 페이지에 추가
|
|
self.page.add(main_content)
|
|
logger.info("UI 구성 완료")
|
|
|
|
def create_file_upload_section(self) -> ft.Container:
|
|
"""파일 업로드 섹션 생성"""
|
|
|
|
# 파일 선택기
|
|
self.file_picker = ft.FilePicker(on_result=self.on_file_selected)
|
|
self.page.overlay.append(self.file_picker)
|
|
|
|
# 선택된 파일 정보
|
|
self.selected_file_text = ft.Text(
|
|
"선택된 파일이 없습니다",
|
|
size=14,
|
|
color=ft.Colors.GREY_600
|
|
)
|
|
|
|
# 파일 선택 버튼
|
|
select_button = ft.ElevatedButton(
|
|
text="PDF 파일 선택",
|
|
icon=ft.Icons.UPLOAD_FILE,
|
|
on_click=self.on_select_file_click,
|
|
style=ft.ButtonStyle(
|
|
bgcolor=ft.Colors.BLUE_100,
|
|
color=ft.Colors.BLUE_800,
|
|
)
|
|
)
|
|
|
|
# 분석 시작 버튼
|
|
self.upload_button = ft.ElevatedButton(
|
|
text="분석 시작",
|
|
icon=ft.Icons.ANALYTICS,
|
|
on_click=self.on_analysis_start_click,
|
|
disabled=True,
|
|
style=ft.ButtonStyle(
|
|
bgcolor=ft.Colors.GREEN_100,
|
|
color=ft.Colors.GREEN_800,
|
|
)
|
|
)
|
|
|
|
return ft.Container(
|
|
content=ft.Column([
|
|
ft.Text(
|
|
"📄 PDF 파일 업로드",
|
|
size=18,
|
|
weight=ft.FontWeight.BOLD,
|
|
color=ft.Colors.BLUE_800
|
|
),
|
|
ft.Divider(),
|
|
ft.Row([
|
|
select_button,
|
|
self.upload_button,
|
|
], alignment=ft.MainAxisAlignment.START),
|
|
self.selected_file_text,
|
|
]),
|
|
padding=20,
|
|
margin=10,
|
|
bgcolor=ft.Colors.WHITE,
|
|
border_radius=10,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
)
|
|
|
|
def create_analysis_settings_section(self) -> ft.Container:
|
|
"""분석 설정 섹션 생성"""
|
|
|
|
# UI 컴포넌트와 참조를 가져오기
|
|
container, organization_selector, page_selector, analysis_mode, custom_prompt = \
|
|
UIComponents.create_analysis_settings_section_with_refs()
|
|
|
|
# 인스턴스 변수에 참조 저장
|
|
self.organization_selector = organization_selector
|
|
self.page_selector = page_selector
|
|
self.analysis_mode = analysis_mode
|
|
self.custom_prompt = custom_prompt
|
|
|
|
# 이벤트 핸들러 설정
|
|
self.analysis_mode.on_change = self.on_analysis_mode_change
|
|
self.organization_selector.on_change = self.on_organization_change
|
|
|
|
return container
|
|
|
|
def create_progress_section(self) -> ft.Container:
|
|
"""진행률 섹션 생성"""
|
|
|
|
# 진행률 바
|
|
self.progress_bar = ft.ProgressBar(
|
|
width=400,
|
|
color=ft.Colors.BLUE_600,
|
|
bgcolor=ft.Colors.GREY_300,
|
|
visible=False,
|
|
)
|
|
|
|
# 상태 텍스트
|
|
self.status_text = ft.Text(
|
|
"대기 중...",
|
|
size=14,
|
|
color=ft.Colors.GREY_600
|
|
)
|
|
|
|
# 진행률 링
|
|
self.progress_ring = ft.ProgressRing(
|
|
width=50,
|
|
height=50,
|
|
stroke_width=4,
|
|
visible=False,
|
|
)
|
|
|
|
return ft.Container(
|
|
content=ft.Column([
|
|
ft.Text(
|
|
"📊 분석 진행 상황",
|
|
size=18,
|
|
weight=ft.FontWeight.BOLD,
|
|
color=ft.Colors.PURPLE_800
|
|
),
|
|
ft.Divider(),
|
|
ft.Row([
|
|
self.progress_ring,
|
|
ft.Column([
|
|
self.status_text,
|
|
self.progress_bar,
|
|
], expand=1),
|
|
], alignment=ft.MainAxisAlignment.START),
|
|
]),
|
|
padding=20,
|
|
margin=10,
|
|
bgcolor=ft.Colors.WHITE,
|
|
border_radius=10,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
)
|
|
|
|
def create_results_section(self) -> ft.Container:
|
|
"""결과 섹션 생성"""
|
|
|
|
# 결과 텍스트
|
|
self.results_text = ft.Text(
|
|
"분석 결과가 여기에 표시됩니다.",
|
|
size=14,
|
|
selectable=True,
|
|
)
|
|
|
|
# 결과 컨테이너
|
|
self.results_container = ft.Container(
|
|
content=ft.Column([
|
|
self.results_text,
|
|
], scroll=ft.ScrollMode.AUTO),
|
|
padding=15,
|
|
height=350,
|
|
bgcolor=ft.Colors.GREY_50,
|
|
border_radius=8,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
)
|
|
|
|
# 저장 버튼들
|
|
save_text_button = ft.ElevatedButton(
|
|
text="텍스트 저장",
|
|
icon=ft.Icons.SAVE,
|
|
disabled=True,
|
|
on_click=self.on_save_text_click,
|
|
style=ft.ButtonStyle(
|
|
bgcolor=ft.Colors.TEAL_100,
|
|
color=ft.Colors.TEAL_800,
|
|
)
|
|
)
|
|
|
|
save_json_button = ft.ElevatedButton(
|
|
text="JSON 저장",
|
|
icon=ft.Icons.SAVE_ALT,
|
|
disabled=True,
|
|
on_click=self.on_save_json_click,
|
|
style=ft.ButtonStyle(
|
|
bgcolor=ft.Colors.INDIGO_100,
|
|
color=ft.Colors.INDIGO_800,
|
|
)
|
|
)
|
|
|
|
# 저장 버튼들을 인스턴스 변수로 저장
|
|
self.save_text_button = save_text_button
|
|
self.save_json_button = save_json_button
|
|
|
|
return ft.Container(
|
|
content=ft.Column([
|
|
ft.Row([
|
|
ft.Text(
|
|
"📋 분석 결과",
|
|
size=18,
|
|
weight=ft.FontWeight.BOLD,
|
|
color=ft.Colors.GREEN_800
|
|
),
|
|
ft.Row([
|
|
save_text_button,
|
|
save_json_button,
|
|
]),
|
|
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
|
ft.Divider(),
|
|
self.results_container,
|
|
]),
|
|
padding=20,
|
|
margin=10,
|
|
bgcolor=ft.Colors.WHITE,
|
|
border_radius=10,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
)
|
|
|
|
def create_pdf_preview_section(self) -> ft.Container:
|
|
"""PDF 미리보기 섹션 생성"""
|
|
|
|
# 미리보기 컨테이너
|
|
self.pdf_preview_container = ft.Container(
|
|
content=ft.Column([
|
|
ft.Icon(
|
|
ft.Icons.PICTURE_AS_PDF,
|
|
size=100,
|
|
color=ft.Colors.GREY_400
|
|
),
|
|
ft.Text(
|
|
"PDF 미리보기",
|
|
size=14,
|
|
color=ft.Colors.GREY_600
|
|
)
|
|
], alignment=ft.MainAxisAlignment.CENTER),
|
|
width=300,
|
|
height=400,
|
|
bgcolor=ft.Colors.GREY_100,
|
|
border_radius=8,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
alignment=ft.alignment.center,
|
|
)
|
|
|
|
# 페이지 네비게이션
|
|
self.prev_button = ft.IconButton(
|
|
icon=ft.Icons.ARROW_BACK,
|
|
disabled=True,
|
|
)
|
|
|
|
self.page_nav_text = ft.Text("1 / 1", size=14)
|
|
|
|
self.next_button = ft.IconButton(
|
|
icon=ft.Icons.ARROW_FORWARD,
|
|
disabled=True,
|
|
)
|
|
|
|
page_nav = ft.Row([
|
|
self.prev_button,
|
|
self.page_nav_text,
|
|
self.next_button,
|
|
], alignment=ft.MainAxisAlignment.CENTER)
|
|
|
|
return ft.Container(
|
|
content=ft.Column([
|
|
ft.Text(
|
|
"👁️ PDF 미리보기",
|
|
size=18,
|
|
weight=ft.FontWeight.BOLD,
|
|
color=ft.Colors.INDIGO_800
|
|
),
|
|
ft.Divider(),
|
|
self.pdf_preview_container,
|
|
page_nav,
|
|
], alignment=ft.MainAxisAlignment.START),
|
|
padding=20,
|
|
margin=10,
|
|
bgcolor=ft.Colors.WHITE,
|
|
border_radius=10,
|
|
border=ft.border.all(1, ft.Colors.GREY_300),
|
|
)
|
|
|
|
# 이벤트 핸들러들
|
|
|
|
def on_select_file_click(self, e):
|
|
"""파일 선택 버튼 클릭 핸들러"""
|
|
self.file_picker.pick_files(
|
|
allowed_extensions=["pdf"],
|
|
allow_multiple=False
|
|
)
|
|
|
|
def on_file_selected(self, e: ft.FilePickerResultEvent):
|
|
"""파일 선택 결과 핸들러"""
|
|
if e.files:
|
|
file = e.files[0]
|
|
self.current_pdf_path = file.path
|
|
|
|
# 파일 검증
|
|
if self.pdf_processor.validate_pdf_file(self.current_pdf_path):
|
|
# PDF 정보 조회
|
|
self.current_pdf_info = self.pdf_processor.get_pdf_info(self.current_pdf_path)
|
|
|
|
# 파일 크기 정보 추가
|
|
file_size_mb = self.current_pdf_info['file_size'] / (1024 * 1024)
|
|
file_info = f"📄 {file.name} ({self.current_pdf_info['page_count']}페이지, {file_size_mb:.1f}MB)"
|
|
self.selected_file_text.value = file_info
|
|
self.selected_file_text.color = ft.Colors.GREEN_600
|
|
self.upload_button.disabled = False
|
|
|
|
# 페이지 네비게이션 업데이트
|
|
self.page_nav_text.value = f"1 / {self.current_pdf_info['page_count']}"
|
|
|
|
logger.info(f"PDF 파일 선택됨: {file.name}")
|
|
else:
|
|
self.selected_file_text.value = "❌ 유효하지 않은 PDF 파일입니다"
|
|
self.selected_file_text.color = ft.Colors.RED_600
|
|
self.upload_button.disabled = True
|
|
self.current_pdf_path = None
|
|
self.current_pdf_info = None
|
|
else:
|
|
self.selected_file_text.value = "선택된 파일이 없습니다"
|
|
self.selected_file_text.color = ft.Colors.GREY_600
|
|
self.upload_button.disabled = True
|
|
self.current_pdf_path = None
|
|
self.current_pdf_info = None
|
|
|
|
self.page.update()
|
|
|
|
def on_analysis_mode_change(self, e):
|
|
"""분석 모드 변경 핸들러"""
|
|
if e.control.value == "custom":
|
|
self.custom_prompt.visible = True
|
|
else:
|
|
self.custom_prompt.visible = False
|
|
self.page.update()
|
|
|
|
def on_organization_change(self, e):
|
|
"""조직 선택 변경 핸들러"""
|
|
selected_org = e.control.value
|
|
logger.info(f"조직 선택 변경: {selected_org}")
|
|
# 필요한 경우 추가 작업 수행
|
|
self.page.update()
|
|
|
|
def on_analysis_start_click(self, e):
|
|
"""분석 시작 버튼 클릭 핸들러"""
|
|
if not self.current_pdf_path or not self.gemini_analyzer:
|
|
return
|
|
|
|
# 분석을 별도 스레드에서 실행
|
|
threading.Thread(target=self.run_analysis, daemon=True).start()
|
|
|
|
def on_save_text_click(self, e):
|
|
"""텍스트 저장 버튼 클릭 핸들러"""
|
|
self._save_results("text")
|
|
|
|
def on_save_json_click(self, e):
|
|
"""JSON 저장 버튼 클릭 핸들러"""
|
|
self._save_results("json")
|
|
|
|
def _save_results(self, format_type: str):
|
|
"""결과 저장 공통 함수"""
|
|
if not self.analysis_results or not self.current_pdf_info:
|
|
self.show_error_dialog("저장 오류", "저장할 분석 결과가 없습니다.")
|
|
return
|
|
|
|
try:
|
|
# 분석 설정 정보 수집
|
|
analysis_settings = {
|
|
"페이지_선택": self.page_selector.value,
|
|
"분석_모드": self.analysis_mode.value,
|
|
"사용자_정의_프롬프트": self.custom_prompt.value if self.analysis_mode.value == "custom" else None,
|
|
"분석_시간": DateTimeUtils.get_timestamp()
|
|
}
|
|
|
|
if format_type == "text":
|
|
# 텍스트 파일로 저장
|
|
saved_path = self.result_saver.save_analysis_results(
|
|
pdf_filename=self.current_pdf_info['filename'],
|
|
analysis_results=self.analysis_results,
|
|
pdf_info=self.current_pdf_info,
|
|
analysis_settings=analysis_settings
|
|
)
|
|
|
|
if saved_path:
|
|
self.show_info_dialog(
|
|
"저장 완료",
|
|
f"분석 결과가 텍스트 파일로 저장되었습니다:\\n\\n{saved_path}"
|
|
)
|
|
else:
|
|
self.show_error_dialog("저장 실패", "텍스트 파일 저장 중 오류가 발생했습니다.")
|
|
|
|
elif format_type == "json":
|
|
# JSON 파일로 저장
|
|
saved_path = self.result_saver.save_analysis_json(
|
|
pdf_filename=self.current_pdf_info['filename'],
|
|
analysis_results=self.analysis_results,
|
|
pdf_info=self.current_pdf_info,
|
|
analysis_settings=analysis_settings
|
|
)
|
|
|
|
if saved_path:
|
|
self.show_info_dialog(
|
|
"저장 완료",
|
|
f"분석 결과가 JSON 파일로 저장되었습니다:\\n\\n{saved_path}"
|
|
)
|
|
else:
|
|
self.show_error_dialog("저장 실패", "JSON 파일 저장 중 오류가 발생했습니다.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"결과 저장 중 오류: {e}")
|
|
self.show_error_dialog("저장 오류", f"결과 저장 중 오류가 발생했습니다:\\n{str(e)}")
|
|
|
|
def run_analysis(self):
|
|
"""분석 실행 (백그라운드 스레드)"""
|
|
try:
|
|
# 분석 시작 시간 기록
|
|
self.analysis_start_time = time.time()
|
|
|
|
# UI 상태 업데이트
|
|
self.update_progress_ui(True, "PDF 이미지 변환 중...")
|
|
|
|
# 조직 유형 결정
|
|
organization_type = "transportation" # 기본값
|
|
if self.organization_selector and self.organization_selector.value:
|
|
if self.organization_selector.value == "한국도로공사":
|
|
organization_type = "expressway"
|
|
else:
|
|
organization_type = "transportation"
|
|
|
|
logger.info(f"선택된 조직 유형: {organization_type}")
|
|
|
|
# 분석할 페이지 결정
|
|
if self.page_selector.value == "첫 번째 페이지":
|
|
pages_to_analyze = [0]
|
|
else: # 모든 페이지
|
|
pages_to_analyze = list(range(self.current_pdf_info['page_count']))
|
|
|
|
# 분석 프롬프트 결정
|
|
if self.analysis_mode.value == "custom":
|
|
prompt = self.custom_prompt.value or Config.DEFAULT_PROMPT
|
|
elif self.analysis_mode.value == "detailed":
|
|
prompt = "이 PDF 이미지를 자세히 분석하여 다음 정보를 제공해주세요: 1) 문서 유형, 2) 주요 내용, 3) 도면/도표 정보, 4) 텍스트 내용, 5) 기타 특징"
|
|
else: # basic
|
|
prompt = Config.DEFAULT_PROMPT
|
|
|
|
# 페이지별 분석 수행
|
|
total_pages = len(pages_to_analyze)
|
|
self.analysis_results = {}
|
|
|
|
for i, page_num in enumerate(pages_to_analyze):
|
|
# 진행률 업데이트
|
|
progress = (i + 1) / total_pages
|
|
self.update_progress_ui(
|
|
True,
|
|
f"페이지 {page_num + 1} 분석 중... ({i + 1}/{total_pages})",
|
|
progress
|
|
)
|
|
|
|
# PDF 페이지를 base64로 변환
|
|
base64_data = self.pdf_processor.pdf_page_to_base64(
|
|
self.current_pdf_path,
|
|
page_num
|
|
)
|
|
|
|
if base64_data:
|
|
# Gemini API로 분석 (조직 유형 전달)
|
|
result = self.gemini_analyzer.analyze_image_from_base64(
|
|
base64_data=base64_data,
|
|
prompt=prompt,
|
|
organization_type=organization_type
|
|
)
|
|
|
|
if result:
|
|
self.analysis_results[page_num] = result
|
|
else:
|
|
self.analysis_results[page_num] = f"페이지 {page_num + 1} 분석 실패"
|
|
else:
|
|
self.analysis_results[page_num] = f"페이지 {page_num + 1} 이미지 변환 실패"
|
|
|
|
# 결과 표시
|
|
self.display_analysis_results()
|
|
|
|
# 완료 상태로 업데이트
|
|
if self.analysis_start_time:
|
|
duration = time.time() - self.analysis_start_time
|
|
duration_str = DateTimeUtils.format_duration(duration)
|
|
self.update_progress_ui(False, f"분석 완료! (소요시간: {duration_str})", 1.0)
|
|
else:
|
|
self.update_progress_ui(False, "분석 완료!", 1.0)
|
|
|
|
except Exception as e:
|
|
logger.error(f"분석 중 오류 발생: {e}")
|
|
self.update_progress_ui(False, f"분석 오류: {str(e)}")
|
|
self.show_error_dialog("분석 오류", f"분석 중 오류가 발생했습니다:\\n{str(e)}")
|
|
|
|
def update_progress_ui(
|
|
self,
|
|
is_running: bool,
|
|
status: str,
|
|
progress: Optional[float] = None
|
|
):
|
|
"""진행률 UI 업데이트"""
|
|
def update():
|
|
self.progress_ring.visible = is_running
|
|
self.status_text.value = status
|
|
|
|
if progress is not None:
|
|
self.progress_bar.value = progress
|
|
self.progress_bar.visible = True
|
|
else:
|
|
self.progress_bar.visible = is_running
|
|
|
|
self.page.update()
|
|
|
|
# 메인 스레드에서 UI 업데이트
|
|
self.page.run_thread(update)
|
|
|
|
def display_analysis_results(self):
|
|
"""분석 결과 표시"""
|
|
def update_results():
|
|
if self.analysis_results:
|
|
# 결과 텍스트 구성 (요약 정보 포함)
|
|
result_text = "📊 분석 요약\\n"
|
|
result_text += f"- 분석된 페이지: {len(self.analysis_results)}개\\n"
|
|
result_text += f"- 분석 완료 시간: {DateTimeUtils.get_timestamp()}\\n\\n"
|
|
|
|
for page_num, result in self.analysis_results.items():
|
|
result_text += f"\\n📋 페이지 {page_num + 1} 분석 결과\\n"
|
|
result_text += "=" * 50 + "\\n"
|
|
result_text += result
|
|
result_text += "\\n\\n"
|
|
|
|
self.results_text.value = result_text.strip()
|
|
|
|
# 저장 버튼 활성화
|
|
self.save_text_button.disabled = False
|
|
self.save_json_button.disabled = False
|
|
else:
|
|
self.results_text.value = "분석 결과가 없습니다."
|
|
self.save_text_button.disabled = True
|
|
self.save_json_button.disabled = True
|
|
|
|
self.page.update()
|
|
|
|
# 메인 스레드에서 UI 업데이트
|
|
self.page.run_thread(update_results)
|
|
|
|
def show_error_dialog(self, title: str, message: str):
|
|
"""오류 다이얼로그 표시"""
|
|
dialog = UIComponents.create_error_dialog(title, message)
|
|
|
|
def close_dialog(e):
|
|
dialog.open = False
|
|
self.page.update()
|
|
|
|
dialog.actions[0].on_click = close_dialog
|
|
self.page.dialog = dialog
|
|
dialog.open = True
|
|
self.page.update()
|
|
|
|
def show_info_dialog(self, title: str, message: str):
|
|
"""정보 다이얼로그 표시"""
|
|
dialog = UIComponents.create_info_dialog(title, message)
|
|
|
|
def close_dialog(e):
|
|
dialog.open = False
|
|
self.page.update()
|
|
|
|
dialog.actions[0].on_click = close_dialog
|
|
self.page.dialog = dialog
|
|
dialog.open = True
|
|
self.page.update()
|
|
|
|
def main(page: ft.Page):
|
|
"""메인 함수"""
|
|
try:
|
|
# 애플리케이션 초기화
|
|
app = PDFAnalyzerApp(page)
|
|
|
|
# UI 구성
|
|
app.build_ui()
|
|
|
|
logger.info("애플리케이션 시작 완료")
|
|
|
|
except Exception as e:
|
|
logger.error(f"애플리케이션 시작 실패: {e}")
|
|
# 간단한 오류 페이지 표시
|
|
page.add(
|
|
ft.Container(
|
|
content=ft.Column([
|
|
ft.Text("애플리케이션 초기화 오류", size=24, weight=ft.FontWeight.BOLD),
|
|
ft.Text(f"오류 내용: {str(e)}", size=16),
|
|
ft.Text("설정을 확인하고 다시 시도하세요.", size=14),
|
|
], alignment=ft.MainAxisAlignment.CENTER),
|
|
alignment=ft.alignment.center,
|
|
expand=True,
|
|
)
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
# 애플리케이션 실행
|
|
ft.app(
|
|
target=main,
|
|
view=ft.AppView.FLET_APP,
|
|
upload_dir="uploads",
|
|
)
|