AI_MMI_Analyser/app/core/settings.py

256 lines
9.4 KiB
Python

"""
Settings management
애플리케이션 설정 관리 모듈
- 일반 앱 설정: ~/.mmi_analyzer/settings.json
- AI 설정(키 포함): ~/.mmi_analyzer/ai_settings.json (별도 파일)
"""
import json
from pathlib import Path
from typing import Optional, Dict, Any
from dataclasses import dataclass, field
@dataclass
class AIProviderConfig:
"""개별 AI 프로바이더 설정"""
api_key: str = ""
model: str = ""
enabled: bool = False
@dataclass
class AISettings:
"""AI 관련 설정"""
current_provider: str = "" # 현재 선택된 프로바이더
providers: Dict[str, Dict[str, Any]] = field(default_factory=dict)
def __post_init__(self):
# 기본 프로바이더 설정 초기화
default_providers = {
"openai": {"api_key": "", "model": "gpt-4o-mini", "enabled": False},
"openrouter": {"api_key": "", "model": "openai/gpt-4o-mini", "enabled": False},
"gemini": {"api_key": "", "model": "gemini-1.5-flash", "enabled": False},
"xai": {"api_key": "", "model": "grok-beta", "enabled": False},
}
for key, value in default_providers.items():
if key not in self.providers:
self.providers[key] = value
def get_api_key(self, provider_type: str) -> str:
"""프로바이더의 API 키 반환"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
if provider_key in self.providers:
return self.providers[provider_key].get("api_key", "")
return ""
def set_api_key(self, provider_type: str, api_key: str):
"""프로바이더의 API 키 설정"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
if provider_key not in self.providers:
self.providers[provider_key] = {}
self.providers[provider_key]["api_key"] = api_key
if api_key:
self.providers[provider_key]["enabled"] = True
def get_model(self, provider_type: str) -> str:
"""프로바이더의 모델 반환"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
if provider_key in self.providers:
return self.providers[provider_key].get("model", "")
return ""
def set_model(self, provider_type: str, model: str):
"""프로바이더의 모델 설정"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
if provider_key not in self.providers:
self.providers[provider_key] = {}
self.providers[provider_key]["model"] = model
def get_current_provider(self) -> Optional[str]:
"""현재 선택된 프로바이더 타입 반환"""
return self.current_provider if self.current_provider else None
def set_current_provider(self, provider_type: str):
"""현재 프로바이더 설정"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
self.current_provider = provider_key
def is_provider_configured(self, provider_type: str) -> bool:
"""프로바이더가 설정되었는지 확인"""
provider_key = provider_type.value if hasattr(provider_type, 'value') else provider_type
if provider_key in self.providers:
api_key = self.providers[provider_key].get("api_key", "")
return bool(api_key)
return False
@dataclass
class AppSettings:
"""전체 애플리케이션 설정"""
ai: AISettings = field(default_factory=AISettings)
theme: str = "dark"
language: str = "ko"
window_geometry: Dict[str, int] = field(default_factory=lambda: {
"x": 100, "y": 100, "width": 1800, "height": 1000
})
recent_files: list = field(default_factory=list)
max_recent_files: int = 10
class SettingsManager:
"""
설정 관리자
설정 파일 저장/로드 및 관리
"""
DEFAULT_SETTINGS_DIR = Path.home() / ".mmi_analyzer"
APP_SETTINGS_FILE = "settings.json"
AI_SETTINGS_FILE = "ai_settings.json"
def __init__(self, settings_dir: Optional[Path] = None):
self._settings_dir = settings_dir or self.DEFAULT_SETTINGS_DIR
self._app_settings_file = self._settings_dir / self.APP_SETTINGS_FILE
self._ai_settings_file = self._settings_dir / self.AI_SETTINGS_FILE
self._settings: Optional[AppSettings] = None
# 설정 디렉토리 생성
self._settings_dir.mkdir(parents=True, exist_ok=True)
@property
def settings(self) -> AppSettings:
"""현재 설정 반환 (없으면 로드)"""
if self._settings is None:
self.load()
return self._settings
@property
def ai_settings(self) -> AISettings:
"""AI 설정 반환"""
return self.settings.ai
def load(self) -> AppSettings:
"""설정 파일에서 로드"""
app_data: Dict[str, Any] = {}
ai_data: Dict[str, Any] = {}
# 1) 앱 설정 로드
if self._app_settings_file.exists():
try:
with open(self._app_settings_file, "r", encoding="utf-8") as f:
app_data = json.load(f) or {}
except Exception as e:
print(f"앱 설정 로드 실패: {e}")
app_data = {}
# 2) AI 설정 로드 (별도 파일)
if self._ai_settings_file.exists():
try:
with open(self._ai_settings_file, "r", encoding="utf-8") as f:
ai_data = json.load(f) or {}
except Exception as e:
print(f"AI 설정 로드 실패: {e}")
ai_data = {}
else:
# 3) 마이그레이션: 과거 settings.json 안의 ai 섹션이 있으면 ai_settings.json로 이동
legacy_ai = (app_data or {}).get("ai")
if isinstance(legacy_ai, dict) and legacy_ai:
ai_data = legacy_ai
try:
# 저장 후 app_data에서 ai 섹션 제거
with open(self._ai_settings_file, "w", encoding="utf-8") as f:
json.dump(ai_data, f, indent=2, ensure_ascii=False)
app_data.pop("ai", None)
with open(self._app_settings_file, "w", encoding="utf-8") as f:
json.dump(app_data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"AI 설정 마이그레이션 실패: {e}")
# AI 설정 복원
ai_settings = AISettings(
current_provider=ai_data.get("current_provider", "") if isinstance(ai_data, dict) else "",
providers=ai_data.get("providers", {}) if isinstance(ai_data, dict) else {},
)
self._settings = AppSettings(
ai=ai_settings,
theme=app_data.get("theme", "dark"),
language=app_data.get("language", "ko"),
window_geometry=app_data.get("window_geometry", {}),
recent_files=app_data.get("recent_files", []),
max_recent_files=app_data.get("max_recent_files", 10),
)
return self._settings
def save(self):
"""설정을 파일에 저장"""
if self._settings is None:
return
# 앱 설정 저장 (AI 제외)
try:
app_data = {
"theme": self._settings.theme,
"language": self._settings.language,
"window_geometry": self._settings.window_geometry,
"recent_files": self._settings.recent_files,
"max_recent_files": self._settings.max_recent_files,
}
with open(self._app_settings_file, "w", encoding="utf-8") as f:
json.dump(app_data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"앱 설정 저장 실패: {e}")
# AI 설정 저장 (별도)
try:
ai_data = {
"current_provider": self._settings.ai.current_provider,
"providers": self._settings.ai.providers,
}
with open(self._ai_settings_file, "w", encoding="utf-8") as f:
json.dump(ai_data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"AI 설정 저장 실패: {e}")
def reset(self):
"""설정 초기화"""
self._settings = AppSettings()
self.save()
def add_recent_file(self, file_path: str):
"""최근 파일 추가"""
if file_path in self.settings.recent_files:
self.settings.recent_files.remove(file_path)
self.settings.recent_files.insert(0, file_path)
# 최대 개수 제한
while len(self.settings.recent_files) > self.settings.max_recent_files:
self.settings.recent_files.pop()
self.save()
# 싱글톤 인스턴스
_settings_manager: Optional[SettingsManager] = None
def get_settings_manager() -> SettingsManager:
"""설정 관리자 싱글톤 인스턴스 반환"""
global _settings_manager
if _settings_manager is None:
_settings_manager = SettingsManager()
return _settings_manager
def get_settings() -> AppSettings:
"""현재 설정 반환"""
return get_settings_manager().settings
def get_ai_settings() -> AISettings:
"""AI 설정 반환"""
return get_settings_manager().ai_settings