first commit
This commit is contained in:
428
advanced_features.py
Normal file
428
advanced_features.py
Normal file
@@ -0,0 +1,428 @@
|
||||
"""
|
||||
고급 기능 모듈
|
||||
PDF 미리보기, 고급 설정, 확장 기능들을 제공합니다.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict, Any
|
||||
import logging
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import flet as ft
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PDFPreviewGenerator:
|
||||
"""PDF 미리보기 생성 클래스"""
|
||||
|
||||
def __init__(self, pdf_processor):
|
||||
self.pdf_processor = pdf_processor
|
||||
self.preview_cache = {}
|
||||
|
||||
def generate_preview(
|
||||
self,
|
||||
file_path: str,
|
||||
page_number: int = 0,
|
||||
max_size: tuple = (300, 400)
|
||||
) -> Optional[str]:
|
||||
"""PDF 페이지 미리보기 이미지 생성 (base64 반환)"""
|
||||
try:
|
||||
cache_key = f"{file_path}_{page_number}_{max_size}"
|
||||
|
||||
# 캐시 확인
|
||||
if cache_key in self.preview_cache:
|
||||
return self.preview_cache[cache_key]
|
||||
|
||||
# PDF 페이지를 이미지로 변환
|
||||
image = self.pdf_processor.convert_pdf_page_to_image(
|
||||
file_path, page_number, zoom=1.0
|
||||
)
|
||||
|
||||
if not image:
|
||||
return None
|
||||
|
||||
# 미리보기 크기로 조정
|
||||
preview_image = self._resize_for_preview(image, max_size)
|
||||
|
||||
# base64로 인코딩
|
||||
buffer = io.BytesIO()
|
||||
preview_image.save(buffer, format='PNG')
|
||||
base64_data = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
# 캐시에 저장
|
||||
self.preview_cache[cache_key] = base64_data
|
||||
|
||||
logger.info(f"PDF 미리보기 생성 완료: 페이지 {page_number + 1}")
|
||||
return base64_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"미리보기 생성 실패: {e}")
|
||||
return None
|
||||
|
||||
def _resize_for_preview(self, image: Image.Image, max_size: tuple) -> Image.Image:
|
||||
"""이미지를 미리보기 크기로 조정"""
|
||||
# 비율 유지하면서 크기 조정
|
||||
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
||||
|
||||
# 캔버스 생성 (회색 배경)
|
||||
canvas = Image.new('RGB', max_size, color='#f0f0f0')
|
||||
|
||||
# 이미지를 중앙에 배치
|
||||
x = (max_size[0] - image.width) // 2
|
||||
y = (max_size[1] - image.height) // 2
|
||||
canvas.paste(image, (x, y))
|
||||
|
||||
return canvas
|
||||
|
||||
def clear_cache(self):
|
||||
"""캐시 정리"""
|
||||
self.preview_cache.clear()
|
||||
logger.info("미리보기 캐시 정리 완료")
|
||||
|
||||
class AdvancedSettings:
|
||||
"""고급 설정 관리 클래스"""
|
||||
|
||||
def __init__(self, settings_file: str = "settings.json"):
|
||||
self.settings_file = Path(settings_file)
|
||||
self.default_settings = {
|
||||
"ui": {
|
||||
"theme_mode": "light",
|
||||
"window_width": 1200,
|
||||
"window_height": 800,
|
||||
"auto_save_results": False,
|
||||
"show_preview": True
|
||||
},
|
||||
"processing": {
|
||||
"default_zoom": 2.0,
|
||||
"image_format": "PNG",
|
||||
"jpeg_quality": 95,
|
||||
"max_pages_batch": 5
|
||||
},
|
||||
"analysis": {
|
||||
"default_mode": "basic",
|
||||
"save_format": "both", # text, json, both
|
||||
"auto_analyze": False,
|
||||
"custom_prompts": []
|
||||
}
|
||||
}
|
||||
self.settings = self.load_settings()
|
||||
|
||||
def load_settings(self) -> Dict[str, Any]:
|
||||
"""설정 파일 로드"""
|
||||
try:
|
||||
if self.settings_file.exists():
|
||||
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
||||
loaded_settings = json.load(f)
|
||||
# 기본 설정과 병합
|
||||
settings = self.default_settings.copy()
|
||||
self._deep_merge(settings, loaded_settings)
|
||||
return settings
|
||||
else:
|
||||
return self.default_settings.copy()
|
||||
except Exception as e:
|
||||
logger.error(f"설정 로드 실패: {e}")
|
||||
return self.default_settings.copy()
|
||||
|
||||
def save_settings(self) -> bool:
|
||||
"""설정 파일 저장"""
|
||||
try:
|
||||
with open(self.settings_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.settings, f, indent=2, ensure_ascii=False)
|
||||
logger.info("설정 저장 완료")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"설정 저장 실패: {e}")
|
||||
return False
|
||||
|
||||
def get(self, section: str, key: str, default=None):
|
||||
"""설정 값 조회"""
|
||||
return self.settings.get(section, {}).get(key, default)
|
||||
|
||||
def set(self, section: str, key: str, value):
|
||||
"""설정 값 변경"""
|
||||
if section not in self.settings:
|
||||
self.settings[section] = {}
|
||||
self.settings[section][key] = value
|
||||
|
||||
def _deep_merge(self, base: dict, update: dict):
|
||||
"""딕셔너리 깊은 병합"""
|
||||
for key, value in update.items():
|
||||
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
||||
self._deep_merge(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
|
||||
class ErrorHandler:
|
||||
"""고급 오류 처리 클래스"""
|
||||
|
||||
def __init__(self):
|
||||
self.error_log = []
|
||||
self.error_callbacks = []
|
||||
|
||||
def handle_error(
|
||||
self,
|
||||
error: Exception,
|
||||
context: str = "",
|
||||
user_message: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""오류 처리 및 정보 수집"""
|
||||
error_info = {
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error_type": type(error).__name__,
|
||||
"error_message": str(error),
|
||||
"context": context,
|
||||
"user_message": user_message or self._get_user_friendly_message(error)
|
||||
}
|
||||
|
||||
# 오류 로그에 추가
|
||||
self.error_log.append(error_info)
|
||||
|
||||
# 로거에 기록
|
||||
logger.error(f"[{context}] {error_info['error_type']}: {error_info['error_message']}")
|
||||
|
||||
# 콜백 실행
|
||||
for callback in self.error_callbacks:
|
||||
try:
|
||||
callback(error_info)
|
||||
except Exception as e:
|
||||
logger.error(f"오류 콜백 실행 실패: {e}")
|
||||
|
||||
return error_info
|
||||
|
||||
def add_error_callback(self, callback):
|
||||
"""오류 발생 시 실행할 콜백 추가"""
|
||||
self.error_callbacks.append(callback)
|
||||
|
||||
def get_recent_errors(self, count: int = 10) -> List[Dict[str, Any]]:
|
||||
"""최근 오류 목록 반환"""
|
||||
return self.error_log[-count:]
|
||||
|
||||
def clear_error_log(self):
|
||||
"""오류 로그 정리"""
|
||||
self.error_log.clear()
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""현재 타임스탬프 반환"""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
def _get_user_friendly_message(self, error: Exception) -> str:
|
||||
"""사용자 친화적 오류 메시지 생성"""
|
||||
error_type = type(error).__name__
|
||||
|
||||
friendly_messages = {
|
||||
"FileNotFoundError": "파일을 찾을 수 없습니다. 파일 경로를 확인해주세요.",
|
||||
"PermissionError": "파일에 접근할 권한이 없습니다. 관리자 권한으로 실행해보세요.",
|
||||
"ConnectionError": "네트워크 연결에 문제가 있습니다. 인터넷 연결을 확인해주세요.",
|
||||
"TimeoutError": "요청 시간이 초과되었습니다. 잠시 후 다시 시도해주세요.",
|
||||
"ValueError": "입력 값이 올바르지 않습니다. 설정을 확인해주세요.",
|
||||
"KeyError": "필요한 설정 값이 없습니다. 설정을 다시 확인해주세요.",
|
||||
"ImportError": "필요한 라이브러리가 설치되지 않았습니다. 의존성을 확인해주세요."
|
||||
}
|
||||
|
||||
return friendly_messages.get(error_type, "예상치 못한 오류가 발생했습니다.")
|
||||
|
||||
class AnalysisHistory:
|
||||
"""분석 히스토리 관리 클래스"""
|
||||
|
||||
def __init__(self, history_file: str = "analysis_history.json"):
|
||||
self.history_file = Path(history_file)
|
||||
self.history = self.load_history()
|
||||
|
||||
def load_history(self) -> List[Dict[str, Any]]:
|
||||
"""히스토리 파일 로드"""
|
||||
try:
|
||||
if self.history_file.exists():
|
||||
with open(self.history_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"히스토리 로드 실패: {e}")
|
||||
return []
|
||||
|
||||
def save_history(self):
|
||||
"""히스토리 파일 저장"""
|
||||
try:
|
||||
with open(self.history_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.history, f, indent=2, ensure_ascii=False)
|
||||
logger.info("히스토리 저장 완료")
|
||||
except Exception as e:
|
||||
logger.error(f"히스토리 저장 실패: {e}")
|
||||
|
||||
def add_analysis(
|
||||
self,
|
||||
pdf_filename: str,
|
||||
analysis_mode: str,
|
||||
pages_analyzed: int,
|
||||
analysis_time: float,
|
||||
success: bool = True
|
||||
):
|
||||
"""분석 기록 추가"""
|
||||
record = {
|
||||
"timestamp": self._get_timestamp(),
|
||||
"pdf_filename": pdf_filename,
|
||||
"analysis_mode": analysis_mode,
|
||||
"pages_analyzed": pages_analyzed,
|
||||
"analysis_time": analysis_time,
|
||||
"success": success
|
||||
}
|
||||
|
||||
self.history.append(record)
|
||||
|
||||
# 최대 100개 기록 유지
|
||||
if len(self.history) > 100:
|
||||
self.history = self.history[-100:]
|
||||
|
||||
self.save_history()
|
||||
|
||||
def get_recent_analyses(self, count: int = 10) -> List[Dict[str, Any]]:
|
||||
"""최근 분석 기록 반환"""
|
||||
return self.history[-count:]
|
||||
|
||||
def get_statistics(self) -> Dict[str, Any]:
|
||||
"""분석 통계 반환"""
|
||||
if not self.history:
|
||||
return {
|
||||
"total_analyses": 0,
|
||||
"successful_analyses": 0,
|
||||
"total_pages": 0,
|
||||
"average_time": 0,
|
||||
"most_used_mode": None
|
||||
}
|
||||
|
||||
successful = [h for h in self.history if h["success"]]
|
||||
|
||||
mode_counts = {}
|
||||
for h in self.history:
|
||||
mode = h["analysis_mode"]
|
||||
mode_counts[mode] = mode_counts.get(mode, 0) + 1
|
||||
|
||||
return {
|
||||
"total_analyses": len(self.history),
|
||||
"successful_analyses": len(successful),
|
||||
"total_pages": sum(h["pages_analyzed"] for h in self.history),
|
||||
"average_time": sum(h["analysis_time"] for h in successful) / len(successful) if successful else 0,
|
||||
"most_used_mode": max(mode_counts.items(), key=lambda x: x[1])[0] if mode_counts else None
|
||||
}
|
||||
|
||||
def clear_history(self):
|
||||
"""히스토리 정리"""
|
||||
self.history.clear()
|
||||
self.save_history()
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""현재 타임스탬프 반환"""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
class CustomPromptManager:
|
||||
"""사용자 정의 프롬프트 관리 클래스"""
|
||||
|
||||
def __init__(self, prompts_file: str = "custom_prompts.json"):
|
||||
self.prompts_file = Path(prompts_file)
|
||||
self.prompts = self.load_prompts()
|
||||
|
||||
def load_prompts(self) -> List[Dict[str, str]]:
|
||||
"""프롬프트 파일 로드"""
|
||||
try:
|
||||
if self.prompts_file.exists():
|
||||
with open(self.prompts_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return self._get_default_prompts()
|
||||
except Exception as e:
|
||||
logger.error(f"프롬프트 로드 실패: {e}")
|
||||
return self._get_default_prompts()
|
||||
|
||||
def save_prompts(self):
|
||||
"""프롬프트 파일 저장"""
|
||||
try:
|
||||
with open(self.prompts_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.prompts, f, indent=2, ensure_ascii=False)
|
||||
logger.info("프롬프트 저장 완료")
|
||||
except Exception as e:
|
||||
logger.error(f"프롬프트 저장 실패: {e}")
|
||||
|
||||
def add_prompt(self, name: str, content: str, description: str = ""):
|
||||
"""새 프롬프트 추가"""
|
||||
prompt = {
|
||||
"name": name,
|
||||
"content": content,
|
||||
"description": description,
|
||||
"created_at": self._get_timestamp()
|
||||
}
|
||||
self.prompts.append(prompt)
|
||||
self.save_prompts()
|
||||
|
||||
def get_prompt(self, name: str) -> Optional[str]:
|
||||
"""프롬프트 내용 조회"""
|
||||
for prompt in self.prompts:
|
||||
if prompt["name"] == name:
|
||||
return prompt["content"]
|
||||
return None
|
||||
|
||||
def get_prompt_list(self) -> List[str]:
|
||||
"""프롬프트 이름 목록 반환"""
|
||||
return [prompt["name"] for prompt in self.prompts]
|
||||
|
||||
def delete_prompt(self, name: str) -> bool:
|
||||
"""프롬프트 삭제"""
|
||||
for i, prompt in enumerate(self.prompts):
|
||||
if prompt["name"] == name:
|
||||
del self.prompts[i]
|
||||
self.save_prompts()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_default_prompts(self) -> List[Dict[str, str]]:
|
||||
"""기본 프롬프트 목록 반환"""
|
||||
return [
|
||||
{
|
||||
"name": "기본 도면 분석",
|
||||
"content": "이 도면을 분석하여 문서 유형, 주요 내용, 치수 정보를 알려주세요.",
|
||||
"description": "일반적인 도면 분석용",
|
||||
"created_at": self._get_timestamp()
|
||||
},
|
||||
{
|
||||
"name": "건축 도면 분석",
|
||||
"content": "이 건축 도면을 분석하여 건물 유형, 평면 구조, 주요 치수, 방의 용도를 상세히 설명해주세요.",
|
||||
"description": "건축 도면 전용",
|
||||
"created_at": self._get_timestamp()
|
||||
},
|
||||
{
|
||||
"name": "기계 도면 분석",
|
||||
"content": "이 기계 도면을 분석하여 부품명, 치수, 공차, 재료, 가공 방법을 파악해주세요.",
|
||||
"description": "기계 설계 도면 전용",
|
||||
"created_at": self._get_timestamp()
|
||||
}
|
||||
]
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""현재 타임스탬프 반환"""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
# 사용 예시
|
||||
if __name__ == "__main__":
|
||||
# 고급 기능 테스트
|
||||
print("고급 기능 모듈 테스트")
|
||||
|
||||
# 설정 관리 테스트
|
||||
settings = AdvancedSettings()
|
||||
print(f"현재 테마: {settings.get('ui', 'theme_mode')}")
|
||||
|
||||
# 오류 처리 테스트
|
||||
error_handler = ErrorHandler()
|
||||
try:
|
||||
raise ValueError("테스트 오류")
|
||||
except Exception as e:
|
||||
error_info = error_handler.handle_error(e, "테스트 컨텍스트")
|
||||
print(f"오류 처리: {error_info['user_message']}")
|
||||
|
||||
# 프롬프트 관리 테스트
|
||||
prompt_manager = CustomPromptManager()
|
||||
prompts = prompt_manager.get_prompt_list()
|
||||
print(f"사용 가능한 프롬프트: {prompts}")
|
||||
Reference in New Issue
Block a user