|
|
|
|
|
""" |
|
|
Configuration Manager |
|
|
|
|
|
Handles all configuration settings for the NZ Legislation Loophole Analysis application. |
|
|
Provides default configurations, persistent storage, and validation. |
|
|
""" |
|
|
|
|
|
import json |
|
|
import os |
|
|
from pathlib import Path |
|
|
from typing import Dict, Any, Optional |
|
|
import streamlit as st |
|
|
|
|
|
class ConfigManager: |
|
|
"""Configuration manager for the application""" |
|
|
|
|
|
def __init__(self, config_file: str = None): |
|
|
""" |
|
|
Initialize configuration manager |
|
|
|
|
|
Args: |
|
|
config_file: Path to configuration file (optional) |
|
|
""" |
|
|
if config_file is None: |
|
|
config_dir = Path(__file__).parent.parent / 'config' |
|
|
config_dir.mkdir(exist_ok=True) |
|
|
config_file = config_dir / 'app_config.json' |
|
|
|
|
|
self.config_file = Path(config_file) |
|
|
self._config = {} |
|
|
self._load_config() |
|
|
|
|
|
def _load_config(self): |
|
|
"""Load configuration from file or use defaults""" |
|
|
if self.config_file.exists(): |
|
|
try: |
|
|
with open(self.config_file, 'r', encoding='utf-8') as f: |
|
|
self._config = json.load(f) |
|
|
|
|
|
self._config = self._merge_with_defaults(self._config) |
|
|
except (json.JSONDecodeError, IOError) as e: |
|
|
print(f"Warning: Could not load config file: {e}") |
|
|
self._config = self._get_default_config() |
|
|
else: |
|
|
self._config = self._get_default_config() |
|
|
|
|
|
def _get_default_config(self) -> Dict[str, Any]: |
|
|
"""Get default configuration""" |
|
|
return { |
|
|
'model': { |
|
|
'path': 'qwen3.gguf', |
|
|
'repo_id': 'DavidAU/Qwen3-Zero-Coder-Reasoning-0.8B-NEO-EX-GGUF', |
|
|
'filename': 'Qwen3-Zero-Coder-Reasoning-0.8B-NEO-EX-D_AU-IQ4_XS-imat.gguf', |
|
|
'context_length': 40960, |
|
|
'max_tokens': 4096, |
|
|
'temperature': 0.3, |
|
|
'top_p': 0.85, |
|
|
'top_k': 50, |
|
|
'repeat_penalty': 1.15 |
|
|
}, |
|
|
'processing': { |
|
|
'chunk_size': 4096, |
|
|
'chunk_overlap': 256, |
|
|
'batch_size': 16, |
|
|
'clean_text': True, |
|
|
'preserve_structure': True |
|
|
}, |
|
|
'cache': { |
|
|
'enabled': True, |
|
|
'max_size_mb': 1024, |
|
|
'ttl_hours': 24, |
|
|
'persistent': True |
|
|
}, |
|
|
'analysis': { |
|
|
'depth': 'Standard', |
|
|
'include_recommendations': True, |
|
|
'focus_areas': ['loopholes', 'ambiguities', 'unintended_consequences'], |
|
|
'legal_domains': ['constitutional', 'administrative', 'criminal', 'civil'] |
|
|
}, |
|
|
'ui': { |
|
|
'theme': 'Auto', |
|
|
'show_progress': True, |
|
|
'auto_refresh': False, |
|
|
'max_display_items': 50 |
|
|
}, |
|
|
'advanced': { |
|
|
'debug_mode': False, |
|
|
'log_level': 'INFO', |
|
|
'memory_limit_mb': 8192, |
|
|
'thread_pool_size': 4, |
|
|
'save_intermediate_results': True |
|
|
} |
|
|
} |
|
|
|
|
|
def _merge_with_defaults(self, user_config: Dict[str, Any]) -> Dict[str, Any]: |
|
|
"""Merge user configuration with defaults""" |
|
|
default_config = self._get_default_config() |
|
|
|
|
|
def merge_dicts(default: Dict[str, Any], user: Dict[str, Any]) -> Dict[str, Any]: |
|
|
merged = default.copy() |
|
|
for key, value in user.items(): |
|
|
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict): |
|
|
merged[key] = merge_dicts(merged[key], value) |
|
|
else: |
|
|
merged[key] = value |
|
|
return merged |
|
|
|
|
|
return merge_dicts(default_config, user_config) |
|
|
|
|
|
def get_config(self) -> Dict[str, Any]: |
|
|
"""Get current configuration""" |
|
|
return self._config.copy() |
|
|
|
|
|
def update_config(self, new_config: Dict[str, Any]): |
|
|
"""Update configuration with validation""" |
|
|
|
|
|
if self._validate_config(new_config): |
|
|
self._config = self._merge_with_defaults(new_config) |
|
|
self._save_config() |
|
|
else: |
|
|
raise ValueError("Invalid configuration provided") |
|
|
|
|
|
def _validate_config(self, config: Dict[str, Any]) -> bool: |
|
|
"""Validate configuration values""" |
|
|
try: |
|
|
|
|
|
model_config = config.get('model', {}) |
|
|
if model_config.get('context_length', 0) < 1024: |
|
|
return False |
|
|
if model_config.get('max_tokens', 0) < 64: |
|
|
return False |
|
|
if not (0 <= model_config.get('temperature', 0) <= 2): |
|
|
return False |
|
|
|
|
|
|
|
|
proc_config = config.get('processing', {}) |
|
|
if proc_config.get('chunk_size', 0) < 256: |
|
|
return False |
|
|
if proc_config.get('chunk_overlap', 0) >= proc_config.get('chunk_size', 1): |
|
|
return False |
|
|
if proc_config.get('batch_size', 0) < 1: |
|
|
return False |
|
|
|
|
|
|
|
|
cache_config = config.get('cache', {}) |
|
|
if cache_config.get('max_size_mb', 0) < 100: |
|
|
return False |
|
|
if cache_config.get('ttl_hours', 0) < 1: |
|
|
return False |
|
|
|
|
|
return True |
|
|
except Exception: |
|
|
return False |
|
|
|
|
|
def _save_config(self): |
|
|
"""Save configuration to file""" |
|
|
try: |
|
|
self.config_file.parent.mkdir(exist_ok=True) |
|
|
with open(self.config_file, 'w', encoding='utf-8') as f: |
|
|
json.dump(self._config, f, indent=2, ensure_ascii=False) |
|
|
except IOError as e: |
|
|
print(f"Warning: Could not save config file: {e}") |
|
|
|
|
|
def reset_to_defaults(self): |
|
|
"""Reset configuration to defaults""" |
|
|
self._config = self._get_default_config() |
|
|
self._save_config() |
|
|
|
|
|
def get_section(self, section: str) -> Dict[str, Any]: |
|
|
"""Get a specific configuration section""" |
|
|
return self._config.get(section, {}) |
|
|
|
|
|
def update_section(self, section: str, values: Dict[str, Any]): |
|
|
"""Update a specific configuration section""" |
|
|
if section not in self._config: |
|
|
self._config[section] = {} |
|
|
|
|
|
self._config[section].update(values) |
|
|
|
|
|
|
|
|
if self._validate_config(self._config): |
|
|
self._save_config() |
|
|
else: |
|
|
raise ValueError(f"Invalid configuration for section: {section}") |
|
|
|
|
|
def export_config(self, filepath: str) -> bool: |
|
|
"""Export configuration to file""" |
|
|
try: |
|
|
with open(filepath, 'w', encoding='utf-8') as f: |
|
|
json.dump(self._config, f, indent=2, ensure_ascii=False) |
|
|
return True |
|
|
except IOError: |
|
|
return False |
|
|
|
|
|
def import_config(self, filepath: str) -> bool: |
|
|
"""Import configuration from file""" |
|
|
try: |
|
|
with open(filepath, 'r', encoding='utf-8') as f: |
|
|
imported_config = json.load(f) |
|
|
|
|
|
if self._validate_config(imported_config): |
|
|
self._config = self._merge_with_defaults(imported_config) |
|
|
self._save_config() |
|
|
return True |
|
|
else: |
|
|
return False |
|
|
except (IOError, json.JSONDecodeError): |
|
|
return False |
|
|
|
|
|
def get_model_config(self) -> Dict[str, Any]: |
|
|
"""Get model-specific configuration""" |
|
|
return self._config.get('model', {}) |
|
|
|
|
|
def get_processing_config(self) -> Dict[str, Any]: |
|
|
"""Get processing-specific configuration""" |
|
|
return self._config.get('processing', {}) |
|
|
|
|
|
def get_cache_config(self) -> Dict[str, Any]: |
|
|
"""Get cache-specific configuration""" |
|
|
return self._config.get('cache', {}) |
|
|
|
|
|
def get_ui_config(self) -> Dict[str, Any]: |
|
|
"""Get UI-specific configuration""" |
|
|
return self._config.get('ui', {}) |
|
|
|
|
|
def get_advanced_config(self) -> Dict[str, Any]: |
|
|
"""Get advanced configuration""" |
|
|
return self._config.get('advanced', {}) |
|
|
|
|
|
|
|
|
_config_instance = None |
|
|
|
|
|
def get_config_manager(config_file: str = None) -> ConfigManager: |
|
|
"""Get or create global configuration manager instance""" |
|
|
global _config_instance |
|
|
|
|
|
if _config_instance is None: |
|
|
_config_instance = ConfigManager(config_file) |
|
|
|
|
|
return _config_instance |
|
|
|