"""Profiles page - manage AI persona profiles.""" from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton, QLabel, QGroupBox, QTextEdit, QLineEdit, QFormLayout, QMessageBox, QSplitter ) from PySide6.QtCore import Qt from ...profiles import list_profiles, save_profile, delete_profile, Profile class ProfilesPage(QWidget): """Profiles management page.""" def __init__(self, main_window): super().__init__() self.main_window = main_window self._setup_ui() self.refresh() def _setup_ui(self): """Set up the UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(24, 24, 24, 24) layout.setSpacing(16) # Header header = QHBoxLayout() title = QLabel("Profiles") title.setObjectName("heading") header.addWidget(title) header.addStretch() self.btn_new = QPushButton("New Profile") self.btn_new.clicked.connect(self._new_profile) header.addWidget(self.btn_new) layout.addLayout(header) # Description desc = QLabel( "AI persona profiles inject system instructions into prompts. " "Select a profile when creating a prompt step to customize the AI's behavior." ) desc.setWordWrap(True) desc.setStyleSheet("color: #718096;") layout.addWidget(desc) # Main content splitter splitter = QSplitter(Qt.Horizontal) # Left: Profile list left = QWidget() left_layout = QVBoxLayout(left) left_layout.setContentsMargins(0, 0, 0, 0) self.profile_list = QListWidget() self.profile_list.currentItemChanged.connect(self._on_profile_selected) left_layout.addWidget(self.profile_list) splitter.addWidget(left) # Right: Profile details/editor right = QWidget() right_layout = QVBoxLayout(right) right_layout.setContentsMargins(0, 0, 0, 0) # Details group details_box = QGroupBox("Profile Details") details_layout = QFormLayout(details_box) details_layout.setSpacing(12) self.name_input = QLineEdit() self.name_input.setPlaceholderText("Profile name") details_layout.addRow("Name:", self.name_input) self.desc_input = QLineEdit() self.desc_input.setPlaceholderText("Brief description") details_layout.addRow("Description:", self.desc_input) right_layout.addWidget(details_box) # System prompt group prompt_box = QGroupBox("System Prompt") prompt_layout = QVBoxLayout(prompt_box) self.prompt_input = QTextEdit() self.prompt_input.setPlaceholderText( "Enter the system instructions that will be injected into prompts.\n\n" "Example: You are a helpful assistant who explains things clearly..." ) prompt_layout.addWidget(self.prompt_input) right_layout.addWidget(prompt_box, 1) # Action buttons actions = QHBoxLayout() self.btn_save = QPushButton("Save") self.btn_save.clicked.connect(self._save_profile) self.btn_save.setEnabled(False) actions.addWidget(self.btn_save) self.btn_delete = QPushButton("Delete") self.btn_delete.setObjectName("danger") self.btn_delete.clicked.connect(self._delete_profile) self.btn_delete.setEnabled(False) actions.addWidget(self.btn_delete) actions.addStretch() right_layout.addLayout(actions) splitter.addWidget(right) splitter.setSizes([300, 500]) layout.addWidget(splitter, 1) # Status label self.status_label = QLabel("") self.status_label.setStyleSheet("color: #718096; font-style: italic;") layout.addWidget(self.status_label) def refresh(self): """Refresh the profile list.""" self.profile_list.clear() profiles = list_profiles() for profile in profiles: item = QListWidgetItem() if profile.builtin: item.setText(f"{profile.name} (built-in)") else: item.setText(profile.name) item.setData(Qt.UserRole, profile) self.profile_list.addItem(item) self._clear_form() self.status_label.setText(f"{len(profiles)} profiles available") def _on_profile_selected(self, current, previous): """Handle profile selection.""" if not current: self._clear_form() return profile = current.data(Qt.UserRole) self._load_profile(profile) def _load_profile(self, profile: Profile): """Load profile into form.""" self.name_input.setText(profile.name) self.desc_input.setText(profile.description) self.prompt_input.setPlainText(profile.system_prompt) # Built-in profiles can't be edited is_builtin = profile.builtin self.name_input.setEnabled(not is_builtin) self.desc_input.setEnabled(not is_builtin) self.prompt_input.setEnabled(not is_builtin) self.btn_save.setEnabled(not is_builtin) self.btn_delete.setEnabled(not is_builtin and profile.name != "None") if is_builtin: self.status_label.setText("Built-in profile (read-only)") else: self.status_label.setText("Custom profile") def _clear_form(self): """Clear the form.""" self.name_input.clear() self.name_input.setEnabled(True) self.desc_input.clear() self.desc_input.setEnabled(True) self.prompt_input.clear() self.prompt_input.setEnabled(True) self.btn_save.setEnabled(False) self.btn_delete.setEnabled(False) def _new_profile(self): """Start creating a new profile.""" self.profile_list.clearSelection() self._clear_form() self.name_input.setFocus() self.btn_save.setEnabled(True) self.status_label.setText("Creating new profile...") def _save_profile(self): """Save the current profile.""" name = self.name_input.text().strip() if not name: QMessageBox.warning(self, "Validation", "Profile name is required") return if name == "None": QMessageBox.warning(self, "Validation", "'None' is a reserved profile name") return description = self.desc_input.text().strip() system_prompt = self.prompt_input.toPlainText().strip() if not system_prompt: QMessageBox.warning(self, "Validation", "System prompt is required") return profile = Profile( name=name, description=description, system_prompt=system_prompt ) try: save_profile(profile) self.main_window.show_status(f"Saved profile '{name}'") self.refresh() # Select the saved profile for i in range(self.profile_list.count()): item = self.profile_list.item(i) if item.data(Qt.UserRole).name == name: self.profile_list.setCurrentItem(item) break except Exception as e: QMessageBox.critical(self, "Error", f"Failed to save profile:\n{e}") def _delete_profile(self): """Delete the selected profile.""" current = self.profile_list.currentItem() if not current: return profile = current.data(Qt.UserRole) if profile.builtin: QMessageBox.warning(self, "Error", "Cannot delete built-in profiles") return reply = QMessageBox.question( self, "Confirm Delete", f"Delete profile '{profile.name}'?\n\nThis cannot be undone.", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: if delete_profile(profile.name): self.main_window.show_status(f"Deleted profile '{profile.name}'") self.refresh() else: QMessageBox.warning(self, "Error", "Failed to delete profile")