| import webbrowser |
| import subprocess |
| from PyQt6.QtWidgets import ( |
| QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, |
| QTextEdit, QLineEdit, QMessageBox, QGroupBox, QProgressBar, |
| QCheckBox, QTabWidget, QWidget, QListWidget, QListWidgetItem |
| ) |
| from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer |
| from PyQt6.QtGui import QFont, QPixmap, QDesktopServices |
| from PyQt6.QtCore import QUrl |
| from pathlib import Path |
|
|
|
|
| def check_required_models_exist(): |
| try: |
| required_models = { |
| 'stable-audio-open-small': [ |
| 'stable-audio-open-small-model.safetensors' |
| ], |
| 'stable-audio-open-1.0': [ |
| 'stable-audio-open-model.safetensors' |
| ] |
| } |
| |
| models_dir = Path('models/pretrained') |
| |
| if not models_dir.exists(): |
| return False, "Models directory does not exist" |
| |
| available_models = [] |
| missing_models = [] |
| |
| for model_name, expected_files in required_models.items(): |
| model_found = False |
| |
| for file_name in expected_files: |
| file_path = models_dir / file_name |
| if file_path.exists() and file_path.is_file(): |
| model_found = True |
| break |
| |
| if not model_found: |
| model_subdir = models_dir / model_name |
| if model_subdir.exists() and model_subdir.is_dir(): |
| safetensors_files = list(model_subdir.glob('*.safetensors')) |
| bin_files = list(model_subdir.glob('*.bin')) |
| model_found = len(safetensors_files) > 0 or len(bin_files) > 0 |
| |
| if model_found: |
| available_models.append(model_name) |
| else: |
| missing_models.append(model_name) |
| |
| if len(available_models) > 0: |
| return True, f"Found models: {', '.join(available_models)}" |
| else: |
| return False, f"Missing models: {', '.join(missing_models)}" |
| |
| except Exception as e: |
| return False, f"Error checking models: {str(e)}" |
|
|
|
|
| def should_show_auth_dialog(): |
| models_exist, message = check_required_models_exist() |
| |
| if not models_exist: |
| return True, f"Models need to be downloaded: {message}" |
| |
| try: |
| from huggingface_hub import HfApi |
| api = HfApi() |
| user = api.whoami() |
| return False, f"Models available and authenticated as: {user}" |
| except Exception: |
| return False, "Models available (authentication not required for existing models)" |
|
|
|
|
| class ModelDownloadThread(QThread): |
| progress_updated = pyqtSignal(str, int, str) |
| download_complete = pyqtSignal(str, bool, str) |
|
|
| def __init__(self, model_ids): |
| super().__init__() |
| self.model_ids = model_ids |
|
|
| def run(self): |
| from app.core.model_manager import ModelManager |
| from app.core.config import get_config |
|
|
| try: |
| config = get_config() |
| manager = ModelManager(config) |
|
|
| for model_id in self.model_ids: |
| try: |
| self.progress_updated.emit( |
| model_id, 0, f"Starting download of {model_id}...") |
|
|
| def progress_callback(percent, message): |
| self.progress_updated.emit(model_id, percent, message) |
|
|
| success = manager.download_model( |
| model_id, progress_callback) |
|
|
| if success: |
| self.progress_updated.emit( |
| model_id, 100, "Download complete!") |
| self.download_complete.emit( |
| model_id, True, "Downloaded successfully") |
| else: |
| self.download_complete.emit( |
| model_id, False, "Download failed") |
|
|
| except Exception as e: |
| self.download_complete.emit( |
| model_id, False, f"Error: {str(e)}") |
|
|
| except Exception as e: |
| for model_id in self.model_ids: |
| self.download_complete.emit( |
| model_id, False, f"Setup error: {str(e)}") |
|
|
|
|
| class HuggingFaceAuthDialog(QDialog): |
|
|
| def __init__(self, parent=None): |
| super().__init__(parent) |
| self.setWindowTitle("Hugging Face Authentication Required") |
| self.setModal(True) |
| self.setMinimumSize(800, 700) |
|
|
| self.step_completed = { |
| 0: False, |
| 1: False, |
| 2: False, |
| 3: False, |
| } |
|
|
| self.selected_models = [] |
| self.check_current_model_status() |
| self.init_ui() |
|
|
| def check_current_model_status(self): |
| try: |
| models_exist, message = check_required_models_exist() |
| self.current_model_status = { |
| 'models_exist': models_exist, |
| 'message': message |
| } |
| print(f"Current model status: {message}") |
| except Exception as e: |
| self.current_model_status = { |
| 'models_exist': False, |
| 'message': f"Error checking models: {str(e)}" |
| } |
| print(f"Error checking current model status: {e}") |
|
|
| def init_ui(self): |
| layout = QVBoxLayout() |
|
|
| header_label = QLabel("Hugging Face Authentication & Model Download") |
| header_label.setFont(QFont("Arial", 16, QFont.Weight.Bold)) |
| header_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| layout.addWidget(header_label) |
|
|
| if hasattr(self, 'current_model_status'): |
| status_text = f"Current Status: {self.current_model_status['message']}" |
| status_color = "#56D364" if self.current_model_status['models_exist'] else "#DB5044" |
| else: |
| status_text = "Checking current model status..." |
| status_color = "#9198A1" |
| |
| status_label = QLabel(status_text) |
| status_label.setStyleSheet(f"color: {status_color}; font-weight: 500; margin: 5px;") |
| status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| layout.addWidget(status_label) |
|
|
| desc_label = QLabel( |
| "Complete all steps to authenticate with Hugging Face and download AI models.\n" |
| "You must complete each step before proceeding to the next one." |
| ) |
| desc_label.setWordWrap(True) |
| desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| layout.addWidget(desc_label) |
|
|
| self.tab_widget = QTabWidget() |
| self.tab_widget.currentChanged.connect(self.update_navigation) |
| layout.addWidget(self.tab_widget) |
|
|
| self.create_terms_tab() |
| self.create_token_tab() |
| self.create_login_tab() |
| self.create_test_tab() |
| self.create_download_tab() |
|
|
| self.create_navigation_buttons(layout) |
|
|
| self.setLayout(layout) |
| self.apply_dialog_styling() |
| self.update_navigation() |
|
|
| def create_terms_tab(self): |
| tab = QWidget() |
| layout = QVBoxLayout() |
|
|
| title = QLabel("Step 1: Accept Model Terms") |
| title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) |
| layout.addWidget(title) |
|
|
| desc = QLabel( |
| "Select which models you want to download and accept their terms.\n" |
| "You need to visit each model page and click 'Accept terms'." |
| ) |
| desc.setWordWrap(True) |
| layout.addWidget(desc) |
|
|
| models_group = QGroupBox("Available Models") |
| models_layout = QVBoxLayout() |
|
|
| self.model_checkboxes = {} |
| models = [ |
| ("stable-audio-open-small", "Stable Audio Open Small", |
| "https://huggingface.co/stabilityai/stable-audio-open-small", "1.68GB - Good for quick generation"), |
| ("stable-audio-open-1.0", "Stable Audio Open 1.0", |
| "https://huggingface.co/stabilityai/stable-audio-open-1.0", "4.85GB - Higher quality, longer audio") |
| ] |
|
|
| for model_id, name, url, description in models: |
| model_widget = QWidget() |
| model_layout = QVBoxLayout() |
|
|
| checkbox = QCheckBox(f"{name}") |
| checkbox.setProperty('model_id', model_id) |
| checkbox.setProperty('url', url) |
| checkbox.toggled.connect(self.on_model_selection_changed) |
| self.model_checkboxes[model_id] = checkbox |
| model_layout.addWidget(checkbox) |
|
|
| desc_label = QLabel(description) |
| desc_label.setStyleSheet( |
| "color: gray; font-size: 11px; margin-left: 20px;") |
| model_layout.addWidget(desc_label) |
|
|
| open_button = QPushButton(f"Open {name} Page") |
| open_button.clicked.connect( |
| lambda checked, u=url: self.open_url(u)) |
| model_layout.addWidget(open_button) |
|
|
| terms_checkbox = QCheckBox(f"I have accepted the terms for {name}") |
| terms_checkbox.setProperty('model_id', model_id) |
| terms_checkbox.toggled.connect(self.on_terms_acceptance_changed) |
| terms_checkbox.setEnabled(False) |
| model_layout.addWidget(terms_checkbox) |
|
|
| model_widget.setLayout(model_layout) |
| models_layout.addWidget(model_widget) |
|
|
| models_group.setLayout(models_layout) |
| layout.addWidget(models_group) |
|
|
| self.step1_complete = QCheckBox( |
| "I have selected models and accepted all required terms") |
| self.step1_complete.toggled.connect( |
| lambda checked: self.mark_step_complete(0, checked)) |
| self.step1_complete.setEnabled(False) |
| layout.addWidget(self.step1_complete) |
|
|
| layout.addStretch() |
| tab.setLayout(layout) |
| self.tab_widget.addTab(tab, "Step 1: Accept Terms") |
|
|
| def create_token_tab(self): |
| tab = QWidget() |
| layout = QVBoxLayout() |
|
|
| title = QLabel("Step 2: Get Your Access Token") |
| title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) |
| layout.addWidget(title) |
|
|
| desc = QLabel( |
| "You need an access token to authenticate with Hugging Face.\n" |
| "This token allows the app to download models on your behalf." |
| ) |
| desc.setWordWrap(True) |
| layout.addWidget(desc) |
|
|
| instructions = QTextEdit() |
| instructions.setPlainText( |
| "1. Go to https://huggingface.co/settings/tokens\n" |
| "2. Click 'New token'\n" |
| "3. Give it a name (e.g., 'Fragmenta Desktop')\n" |
| "4. Select 'Read' role\n" |
| "5. IMPORTANT: Enable 'Read access to public gated repositories'\n" |
| "6. Click 'Generate token'\n" |
| "7. Copy the token (you'll need it in the next step)\n" |
| "8. Keep this token safe - you won't see it again!" |
| ) |
| instructions.setMaximumHeight(150) |
| layout.addWidget(instructions) |
|
|
| token_button = QPushButton("Open Token Settings Page") |
| token_button.clicked.connect(lambda: self.open_url( |
| "https://huggingface.co/settings/tokens")) |
| layout.addWidget(token_button) |
|
|
| self.step2_complete = QCheckBox("I have generated my access token") |
| self.step2_complete.toggled.connect( |
| lambda checked: self.mark_step_complete(1, checked)) |
| layout.addWidget(self.step2_complete) |
|
|
| layout.addStretch() |
| tab.setLayout(layout) |
| self.tab_widget.addTab(tab, "Step 2: Get Token") |
|
|
| def create_login_tab(self): |
| tab = QWidget() |
| layout = QVBoxLayout() |
|
|
| title = QLabel("Step 3: Login with Your Token") |
| title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) |
| layout.addWidget(title) |
|
|
| desc = QLabel( |
| "Enter your access token below to authenticate with Hugging Face.\n" |
| "This will allow the app to download models." |
| ) |
| desc.setWordWrap(True) |
| layout.addWidget(desc) |
|
|
| token_label = QLabel("Access Token:") |
| layout.addWidget(token_label) |
|
|
| self.token_input = QLineEdit() |
| self.token_input.setEchoMode(QLineEdit.EchoMode.Password) |
| self.token_input.setPlaceholderText("hf_... (paste your token here)") |
| layout.addWidget(self.token_input) |
|
|
| self.login_button = QPushButton("Login with Token") |
| self.login_button.clicked.connect(self.login_with_token) |
| layout.addWidget(self.login_button) |
|
|
| self.login_status = QLabel("") |
| layout.addWidget(self.login_status) |
|
|
| layout.addStretch() |
| tab.setLayout(layout) |
| self.tab_widget.addTab(tab, "Step 3: Login") |
|
|
| def create_test_tab(self): |
| tab = QWidget() |
| layout = QVBoxLayout() |
|
|
| title = QLabel("Step 4: Test Authentication") |
| title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) |
| layout.addWidget(title) |
|
|
| desc = QLabel( |
| "Click the button below to verify your authentication is working correctly." |
| ) |
| desc.setWordWrap(True) |
| layout.addWidget(desc) |
|
|
| self.test_button = QPushButton("Test Authentication") |
| self.test_button.clicked.connect(self.test_authentication) |
| layout.addWidget(self.test_button) |
|
|
| self.test_status = QLabel("") |
| layout.addWidget(self.test_status) |
|
|
| self.success_label = QLabel("") |
| self.success_label.setStyleSheet("color: green; font-weight: bold;") |
| layout.addWidget(self.success_label) |
|
|
| layout.addStretch() |
| tab.setLayout(layout) |
| self.tab_widget.addTab(tab, "Step 4: Test") |
|
|
| def create_download_tab(self): |
| tab = QWidget() |
| layout = QVBoxLayout() |
|
|
| title = QLabel("Step 5: Download Models") |
| title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) |
| layout.addWidget(title) |
|
|
| self.download_desc = QLabel( |
| "Ready to download the selected models to models/pretrained/") |
| self.download_desc.setWordWrap(True) |
| layout.addWidget(self.download_desc) |
|
|
| self.selected_models_list = QListWidget() |
| layout.addWidget(self.selected_models_list) |
|
|
| self.download_button = QPushButton("Start Download") |
| self.download_button.clicked.connect(self.start_model_download) |
| layout.addWidget(self.download_button) |
|
|
| self.progress_area = QWidget() |
| progress_layout = QVBoxLayout() |
| self.progress_bars = {} |
| self.progress_area.setLayout(progress_layout) |
| layout.addWidget(self.progress_area) |
|
|
| self.download_status = QLabel("") |
| layout.addWidget(self.download_status) |
|
|
| layout.addStretch() |
| tab.setLayout(layout) |
| self.tab_widget.addTab(tab, "Step 5: Download") |
|
|
| def create_navigation_buttons(self, parent_layout): |
| button_layout = QHBoxLayout() |
|
|
| self.prev_button = QPushButton("← Previous") |
| self.prev_button.clicked.connect(self.go_previous) |
| button_layout.addWidget(self.prev_button) |
|
|
| button_layout.addStretch() |
|
|
| help_button = QPushButton("Help") |
| help_button.clicked.connect(self.show_help) |
| button_layout.addWidget(help_button) |
|
|
| self.next_button = QPushButton("Next →") |
| self.next_button.clicked.connect(self.go_next) |
| button_layout.addWidget(self.next_button) |
|
|
| self.close_button = QPushButton("Close") |
| self.close_button.clicked.connect(self.accept) |
| button_layout.addWidget(self.close_button) |
|
|
| parent_layout.addLayout(button_layout) |
|
|
| def on_model_selection_changed(self): |
| sender = self.sender() |
| model_id = sender.property('model_id') |
|
|
| for widget in self.findChildren(QCheckBox): |
| if widget.property('model_id') == model_id and 'accepted the terms' in widget.text(): |
| widget.setEnabled(sender.isChecked()) |
| if not sender.isChecked(): |
| widget.setChecked(False) |
| break |
|
|
| self.update_selected_models() |
| self.check_step1_completion() |
|
|
| def on_terms_acceptance_changed(self): |
| self.check_step1_completion() |
|
|
| def check_step1_completion(self): |
| selected_models = [] |
| all_terms_accepted = True |
|
|
| for model_id, checkbox in self.model_checkboxes.items(): |
| if checkbox.isChecked(): |
| selected_models.append(model_id) |
| terms_accepted = False |
| for widget in self.findChildren(QCheckBox): |
| if (widget.property('model_id') == model_id and |
| 'accepted the terms' in widget.text() and |
| widget.isChecked()): |
| terms_accepted = True |
| break |
|
|
| if not terms_accepted: |
| all_terms_accepted = False |
|
|
| can_complete = len(selected_models) > 0 and all_terms_accepted |
| self.step1_complete.setEnabled(can_complete) |
|
|
| if can_complete and not self.step1_complete.isChecked(): |
| self.step1_complete.setChecked(True) |
| elif not can_complete: |
| self.step1_complete.setChecked(False) |
|
|
| def update_selected_models(self): |
| self.selected_models = [] |
| for model_id, checkbox in self.model_checkboxes.items(): |
| if checkbox.isChecked(): |
| self.selected_models.append(model_id) |
|
|
| self.selected_models_list.clear() |
| for model_id in self.selected_models: |
| item = QListWidgetItem(f"{model_id}") |
| self.selected_models_list.addItem(item) |
|
|
| self.download_desc.setText( |
| f"Ready to download {len(self.selected_models)} selected model(s) to models/pretrained/" |
| ) |
|
|
| def mark_step_complete(self, step_index, completed): |
| self.step_completed[step_index] = completed |
| self.update_navigation() |
|
|
| def update_navigation(self): |
| if not hasattr(self, 'prev_button'): |
| return |
|
|
| current_tab = self.tab_widget.currentIndex() |
|
|
| self.prev_button.setEnabled(current_tab > 0) |
|
|
| can_go_next = False |
| if current_tab < 4: |
| if current_tab == 0: |
| can_go_next = self.step_completed[0] |
| elif current_tab == 1: |
| can_go_next = self.step_completed[1] |
| elif current_tab == 2: |
| can_go_next = self.step_completed[2] |
| elif current_tab == 3: |
| can_go_next = self.step_completed[3] |
|
|
| self.next_button.setEnabled(can_go_next) |
|
|
| if current_tab == 3: |
| self.next_button.setText("Download →") |
| else: |
| self.next_button.setText("Next →") |
|
|
| self.close_button.setVisible(current_tab == 4) |
|
|
| for i in range(5): |
| tab_enabled = True |
| if i > 0 and not self.step_completed[i-1]: |
| tab_enabled = False |
| self.tab_widget.setTabEnabled(i, tab_enabled) |
|
|
| def go_previous(self): |
| current = self.tab_widget.currentIndex() |
| if current > 0: |
| self.tab_widget.setCurrentIndex(current - 1) |
| self.update_navigation() |
|
|
| def go_next(self): |
| current = self.tab_widget.currentIndex() |
| if current < 4: |
| self.tab_widget.setCurrentIndex(current + 1) |
| self.update_navigation() |
|
|
| def open_url(self, url): |
| try: |
| webbrowser.open(url) |
| except Exception as e: |
| self.show_modern_message("Error", f"Could not open browser: {e}", "error") |
|
|
| def login_with_token(self): |
| token = self.token_input.text().strip() |
|
|
| if not token: |
| self.show_modern_message("Error", "Please enter your access token", "warning") |
| return |
|
|
| if not token.startswith("hf_"): |
| self.show_modern_message("Error", "Token should start with 'hf_'", "warning") |
| return |
|
|
| try: |
| from huggingface_hub import HfApi |
| api = HfApi() |
|
|
| api.token = token |
| user = api.whoami() |
|
|
| import subprocess |
| result = subprocess.run( |
| ['huggingface-cli', 'login', '--token', token], |
| capture_output=True, text=True |
| ) |
|
|
| if result.returncode == 0: |
| self.login_status.setText( |
| f"Successfully logged in as: {user}") |
| self.login_status.setStyleSheet( |
| "color: green; font-weight: bold;") |
| self.mark_step_complete(2, True) |
| self.show_modern_message( |
| "Success", f"Successfully logged in as {user}", "info") |
| else: |
| self.login_status.setText(f"Login failed: {result.stderr}") |
| self.login_status.setStyleSheet("color: red;") |
|
|
| except Exception as e: |
| self.login_status.setText(f"Error: {str(e)}") |
| self.login_status.setStyleSheet("color: red;") |
|
|
| def test_authentication(self): |
| try: |
| from huggingface_hub import HfApi |
| api = HfApi() |
| user = api.whoami() |
|
|
| self.test_status.setText(f"Authenticated as: {user}") |
| self.test_status.setStyleSheet("color: green; font-weight: bold;") |
|
|
| self.success_label.setText( |
| "Authentication successful!\n" |
| "You can now download models." |
| ) |
|
|
| self.mark_step_complete(3, True) |
|
|
| except Exception as e: |
| self.test_status.setText(f"Not authenticated: {str(e)}") |
| self.test_status.setStyleSheet("color: red;") |
| self.success_label.setText("") |
|
|
| def start_model_download(self): |
| if not self.selected_models: |
| self.show_modern_message( |
| "Error", "No models selected for download", "warning") |
| return |
|
|
| self.download_button.setEnabled(False) |
| self.download_button.setText("Downloading...") |
|
|
| layout = self.progress_area.layout() |
| for i in reversed(range(layout.count())): |
| layout.itemAt(i).widget().setParent(None) |
|
|
| self.progress_bars = {} |
| for model_id in self.selected_models: |
| label = QLabel(f"{model_id}") |
| layout.addWidget(label) |
|
|
| progress_bar = QProgressBar() |
| progress_bar.setRange(0, 100) |
| progress_bar.setValue(0) |
| layout.addWidget(progress_bar) |
|
|
| status_label = QLabel("Preparing...") |
| layout.addWidget(status_label) |
|
|
| self.progress_bars[model_id] = { |
| 'bar': progress_bar, |
| 'status': status_label |
| } |
|
|
| self.download_thread = ModelDownloadThread(self.selected_models) |
| self.download_thread.progress_updated.connect( |
| self.on_download_progress) |
| self.download_thread.download_complete.connect( |
| self.on_download_complete) |
| self.download_thread.start() |
|
|
| def on_download_progress(self, model_id, percent, message): |
| if model_id in self.progress_bars: |
| self.progress_bars[model_id]['bar'].setValue(percent) |
| self.progress_bars[model_id]['status'].setText(message) |
|
|
| def on_download_complete(self, model_id, success, message): |
| if model_id in self.progress_bars: |
| if success: |
| self.progress_bars[model_id]['status'].setText("[SUCCESS] " + message) |
| else: |
| self.progress_bars[model_id]['status'].setText("[FAILED] " + message) |
|
|
| all_complete = True |
| for model_id in self.selected_models: |
| if model_id in self.progress_bars: |
| status_text = self.progress_bars[model_id]['status'].text() |
| if not (status_text.startswith("[SUCCESS]") or status_text.startswith("[FAILED]")): |
| all_complete = False |
| break |
|
|
| if all_complete: |
| self.download_button.setEnabled(True) |
| self.download_button.setText("Download Complete") |
| self.download_status.setText( |
| "All downloads completed! Models are ready to use.") |
| self.download_status.setStyleSheet( |
| "color: green; font-weight: bold;") |
|
|
| def show_help(self): |
| self.show_modern_help_dialog() |
|
|
| def show_modern_help_dialog(self): |
| from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QScrollArea |
| from PyQt6.QtCore import Qt |
| from PyQt6.QtGui import QFont |
| |
| dialog = QDialog(self) |
| dialog.setWindowTitle("Help - Hugging Face Authentication") |
| dialog.setFixedSize(580, 450) |
| dialog.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowCloseButtonHint) |
| |
| main_layout = QVBoxLayout() |
| main_layout.setSpacing(15) |
| main_layout.setContentsMargins(25, 25, 25, 20) |
| |
| title_label = QLabel("Hugging Face Authentication Help") |
| title_font = QFont("Helvetica Neue", 18, QFont.Weight.Bold) |
| title_label.setFont(title_font) |
| title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| title_label.setStyleSheet("color: #F0F6FC; margin-bottom: 15px;") |
| |
| scroll_area = QScrollArea() |
| scroll_area.setWidgetResizable(True) |
| scroll_area.setStyleSheet(""" |
| QScrollArea { |
| border: none; |
| background: transparent; |
| } |
| QScrollBar:vertical { |
| background: #161B22; |
| width: 8px; |
| border-radius: 4px; |
| } |
| QScrollBar::handle:vertical { |
| background: #484F58; |
| border-radius: 4px; |
| } |
| QScrollBar::handle:vertical:hover { |
| background: #6E7681; |
| } |
| """) |
| |
| content_widget = QWidget() |
| content_widget.setStyleSheet("background: transparent;") |
| content_layout = QVBoxLayout() |
| content_layout.setSpacing(20) |
| |
| help_sections = [ |
| ("Why do I need to authenticate?", |
| "Stable Audio models are gated and require you to accept terms and authenticate with Hugging Face before downloading."), |
| ("What is an access token?", |
| "An access token is like a password that allows the app to download models on your behalf. It's stored securely on your computer."), |
| ("IMPORTANT: Token Permissions", |
| "Your token MUST have 'Read access to public gated repositories' enabled. This is required to download Stable Audio models."), |
| ("Is this safe?", |
| "Yes! The token only has 'Read' permissions, meaning it can only download models. It cannot modify your account or upload anything."), |
| ("What if I lose my token?", |
| "You can always generate a new token from the Hugging Face settings page."), |
| ("Where are models downloaded?", |
| "Models are downloaded to the models/pretrained/ directory in your Fragmenta Desktop installation.") |
| ] |
| |
| for question, answer in help_sections: |
| q_label = QLabel(question) |
| q_font = QFont("Helvetica Neue", 13, QFont.Weight.Bold) |
| q_label.setFont(q_font) |
| q_label.setStyleSheet("color: #F0F6FC; margin-bottom: 5px;") |
| |
| a_label = QLabel(answer) |
| a_font = QFont("Helvetica Neue", 12) |
| a_label.setFont(a_font) |
| a_label.setWordWrap(True) |
| a_label.setStyleSheet(""" |
| color: #C9D1D9; |
| line-height: 1.5; |
| padding: 8px 15px; |
| background: transparent; |
| border-left: 3px solid #3a6fec; |
| border-radius: 4px; |
| margin-bottom: 10px; |
| """) |
| |
| content_layout.addWidget(q_label) |
| content_layout.addWidget(a_label) |
| |
| content_widget.setLayout(content_layout) |
| scroll_area.setWidget(content_widget) |
| |
| button_layout = QHBoxLayout() |
| button_layout.addStretch() |
| |
| ok_btn = QPushButton("Got it!") |
| ok_btn.setFixedSize(100, 35) |
| ok_btn.setStyleSheet(""" |
| QPushButton { |
| background-color: #3498db; |
| color: white; |
| border: none; |
| border-radius: 8px; |
| font-family: 'Helvetica Neue', Arial, sans-serif; |
| font-size: 13px; |
| font-weight: 500; |
| } |
| QPushButton:hover { |
| background-color: #2980b9; |
| border: 2px solid #3498db; |
| } |
| QPushButton:pressed { |
| background-color: #21618c; |
| border: 2px solid #1f5582; |
| } |
| """) |
| |
| button_layout.addWidget(ok_btn) |
| button_layout.addStretch() |
| |
| main_layout.addWidget(title_label) |
| main_layout.addWidget(scroll_area) |
| main_layout.addLayout(button_layout) |
| |
| dialog.setLayout(main_layout) |
| |
| dialog.setStyleSheet(""" |
| QDialog { |
| background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, |
| stop: 0 #161B22, stop: 1 #0D1117); |
| border: 1px solid #30363D; |
| border-radius: 12px; |
| } |
| """) |
| |
| ok_btn.clicked.connect(dialog.accept) |
| dialog.exec() |
|
|
| def show_modern_message(self, title, message, msg_type="info"): |
| from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton |
| from PyQt6.QtCore import Qt |
| from PyQt6.QtGui import QFont |
| |
| dialog = QDialog(self) |
| dialog.setWindowTitle(title) |
| dialog.setFixedSize(400, 200) |
| dialog.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowCloseButtonHint) |
| |
| main_layout = QVBoxLayout() |
| main_layout.setSpacing(20) |
| main_layout.setContentsMargins(25, 25, 25, 20) |
| |
| title_label = QLabel(title) |
| title_font = QFont("Helvetica Neue", 16, QFont.Weight.Bold) |
| title_label.setFont(title_font) |
| title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| |
| if msg_type == "warning" or msg_type == "error": |
| title_label.setStyleSheet("color: #DB5044; margin-bottom: 10px;") |
| else: |
| title_label.setStyleSheet("color: #F0F6FC; margin-bottom: 10px;") |
| |
| message_label = QLabel(message) |
| message_font = QFont("Helvetica Neue", 12) |
| message_label.setFont(message_font) |
| message_label.setWordWrap(True) |
| message_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
| message_label.setStyleSheet(""" |
| color: #C9D1D9; |
| line-height: 1.5; |
| padding: 15px; |
| background: transparent; |
| """) |
| |
| button_layout = QHBoxLayout() |
| button_layout.addStretch() |
| |
| ok_btn = QPushButton("OK") |
| ok_btn.setFixedSize(80, 35) |
| |
| if msg_type == "warning" or msg_type == "error": |
| ok_btn.setStyleSheet(""" |
| QPushButton { |
| background-color: #DB5044; |
| color: #FFFFFF; |
| border: 1px solid #E85D75; |
| border-radius: 8px; |
| font-family: 'Helvetica Neue', Arial, sans-serif; |
| font-size: 13px; |
| font-weight: 500; |
| } |
| QPushButton:hover { |
| background-color: #E85D75; |
| border: 2px solid #FF6B7A; |
| } |
| QPushButton:pressed { |
| background-color: #C03543; |
| border: 2px solid #A02632; |
| } |
| """) |
| else: |
| ok_btn.setStyleSheet(""" |
| QPushButton { |
| background-color: #3a6fec; |
| color: #FFFFFF; |
| border: 1px solid #4078f2; |
| border-radius: 8px; |
| font-family: 'Helvetica Neue', Arial, sans-serif; |
| font-size: 13px; |
| font-weight: 500; |
| } |
| QPushButton:hover { |
| background-color: #4078f2; |
| border: 2px solid #5087ff; |
| } |
| QPushButton:pressed { |
| background-color: #2c5aa0; |
| border: 2px solid #1f4788; |
| } |
| """) |
| |
| button_layout.addWidget(ok_btn) |
| button_layout.addStretch() |
| |
| main_layout.addWidget(title_label) |
| main_layout.addWidget(message_label) |
| main_layout.addStretch() |
| main_layout.addLayout(button_layout) |
| |
| dialog.setLayout(main_layout) |
| |
| dialog.setStyleSheet(""" |
| QDialog { |
| background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, |
| stop: 0 #161B22, stop: 1 #0D1117); |
| border: 1px solid #30363D; |
| border-radius: 12px; |
| } |
| """) |
| |
| ok_btn.clicked.connect(dialog.accept) |
| dialog.exec() |
|
|
| def apply_dialog_styling(self): |
| stylesheet = """ |
| QDialog { |
| background-color: #0D1117; |
| color: #F0F6FC; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| } |
| |
| QDialog::title { |
| background-color: #161B22; |
| color: #F0F6FC; |
| } |
| |
| QLabel { |
| color: #C9D1D9; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| } |
| |
| QPushButton { |
| background-color: #3a6fec; |
| color: #FFFFFF; |
| border: 1px solid #4078f2; |
| border-radius: 6px; |
| padding: 8px 16px; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| font-weight: 500; |
| } |
| |
| QPushButton:hover { |
| background-color: #4078f2; |
| border-color: #5087ff; |
| } |
| |
| QPushButton:pressed { |
| background-color: #2c5aa0; |
| } |
| |
| QPushButton:disabled { |
| background-color: #484F58; |
| color: #8B949E; |
| border-color: #30363D; |
| } |
| |
| QTabWidget::pane { |
| border: 1px solid #30363D; |
| background-color: #161B22; |
| } |
| |
| QTabBar::tab { |
| background-color: #21262D; |
| color: #C9D1D9; |
| padding: 8px 16px; |
| border: 1px solid #30363D; |
| border-bottom: none; |
| border-radius: 6px 6px 0 0; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| } |
| |
| QTabBar::tab:selected { |
| background-color: #161B22; |
| color: #3a6fec; |
| font-weight: 500; |
| border-color: #30363D; |
| } |
| |
| QTabBar::tab:hover { |
| background-color: #262C36; |
| } |
| |
| QGroupBox { |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| font-weight: 500; |
| color: #F0F6FC; |
| border: 1px solid #30363D; |
| border-radius: 6px; |
| margin-top: 8px; |
| padding-top: 8px; |
| background-color: #161B22; |
| } |
| |
| QGroupBox::title { |
| subcontrol-origin: margin; |
| left: 8px; |
| padding: 0 4px 0 4px; |
| color: #F0F6FC; |
| } |
| |
| QCheckBox { |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| color: #C9D1D9; |
| background-color: transparent; |
| } |
| |
| QCheckBox:disabled { |
| color: #6E7681; |
| } |
| |
| QCheckBox::indicator { |
| width: 18px; |
| height: 18px; |
| border: 2px solid #30363D; |
| border-radius: 4px; |
| background-color: #0D1117; |
| } |
| |
| QCheckBox::indicator:checked { |
| background-color: #3a6fec; |
| border: 2px solid #3a6fec; |
| image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDNMNC41IDguNUwyIDYiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+); |
| } |
| |
| QCheckBox::indicator:unchecked { |
| background-color: #0D1117; |
| border: 2px solid #30363D; |
| } |
| |
| QCheckBox::indicator:checked:hover { |
| background-color: #4078f2; |
| border: 2px solid #4078f2; |
| } |
| |
| QCheckBox::indicator:unchecked:hover { |
| border: 2px solid #484F58; |
| background-color: #161B22; |
| } |
| |
| QCheckBox::indicator:disabled { |
| background-color: #161B22; |
| border: 2px solid #484F58; |
| } |
| |
| QTextEdit { |
| border: 1px solid #30363D; |
| border-radius: 6px; |
| padding: 8px; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| background-color: #0D1117; |
| color: #F0F6FC; |
| } |
| |
| QLineEdit { |
| border: 1px solid #30363D; |
| border-radius: 6px; |
| padding: 8px; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| background-color: #0D1117; |
| color: #F0F6FC; |
| } |
| |
| QLineEdit:focus { |
| border-color: #3a6fec; |
| } |
| |
| QTextEdit:focus { |
| border-color: #3a6fec; |
| } |
| |
| QProgressBar { |
| border: 1px solid #30363D; |
| border-radius: 6px; |
| text-align: center; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| background-color: #161B22; |
| color: #F0F6FC; |
| } |
| |
| QProgressBar::chunk { |
| background-color: #3a6fec; |
| border-radius: 5px; |
| } |
| |
| QListWidget { |
| background-color: #0D1117; |
| color: #C9D1D9; |
| border: 1px solid #30363D; |
| border-radius: 6px; |
| font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| font-size: 13px; |
| } |
| |
| QListWidget::item { |
| padding: 8px; |
| border-bottom: 1px solid #21262D; |
| } |
| |
| QListWidget::item:selected { |
| background-color: #3a6fec; |
| color: #FFFFFF; |
| } |
| |
| QListWidget::item:hover { |
| background-color: #21262D; |
| } |
| |
| QScrollBar:vertical { |
| background-color: #161B22; |
| width: 12px; |
| border-radius: 6px; |
| } |
| |
| QScrollBar::handle:vertical { |
| background-color: #484F58; |
| border-radius: 6px; |
| min-height: 20px; |
| } |
| |
| QScrollBar::handle:vertical:hover { |
| background-color: #6E7681; |
| } |
| """ |
| self.setStyleSheet(stylesheet) |
|
|
|
|
| def show_hf_auth_dialog(parent=None): |
| should_show, reason = should_show_auth_dialog() |
| |
| if not should_show: |
| print(f"Skipping authentication dialog: {reason}") |
| return True |
| |
| print(f"Showing authentication dialog: {reason}") |
| dialog = HuggingFaceAuthDialog(parent) |
| return dialog.exec() == QDialog.DialogCode.Accepted |
|
|
|
|
| def show_hf_auth_dialog_force(parent=None): |
| print("Showing authentication dialog (forced)") |
| dialog = HuggingFaceAuthDialog(parent) |
| return dialog.exec() == QDialog.DialogCode.Accepted |
|
|