diff --git a/main.py b/main.py index c3674d0..9a89921 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ from modules.console_window import ConsoleWindow from modules.model_manager import ModelManager from modules.sampler_manager import SamplerManager from modules.prompt_window import PromptWindow +from modules.negative_prompt_window import NegativePromptWindow class GenerationThread(QThread): progress_updated = pyqtSignal(float, str) @@ -64,6 +65,7 @@ class MainWindow(QMainWindow): self.api_url = "http://localhost:7860" # URL по умолчанию self.console_window = None self.prompt_window = None + self.negative_prompt_window = None self.progress_timer = None self.init_ui() @@ -164,29 +166,32 @@ class MainWindow(QMainWindow): settings_layout.addLayout(api_layout) # Промпт - prompt_label = QLabel("Промпт:") - self.prompt_input = QTextEdit() - self.prompt_input.setPlaceholderText("Введите описание изображения...") prompt_layout = QVBoxLayout() prompt_header = QHBoxLayout() - prompt_header.addWidget(prompt_label) - - # Добавляем кнопку промптов рядом с полем промпта + prompt_label = QLabel("Промпт:") + self.prompt_input = QTextEdit() + self.prompt_input.setMinimumHeight(100) + prompt_buttons = QHBoxLayout() self.prompt_button = QPushButton("Готовые промпты") self.prompt_button.clicked.connect(self.toggle_prompt_window) - prompt_header.addWidget(self.prompt_button) - prompt_header.addStretch() - + self.negative_prompt_button = QPushButton("Негативные промпты") + self.negative_prompt_button.clicked.connect(self.toggle_negative_prompt_window) + prompt_buttons.addWidget(self.prompt_button) + prompt_buttons.addWidget(self.negative_prompt_button) + prompt_header.addWidget(prompt_label) + prompt_header.addLayout(prompt_buttons) prompt_layout.addLayout(prompt_header) prompt_layout.addWidget(self.prompt_input) settings_layout.addLayout(prompt_layout) # Негативный промпт - neg_prompt_label = QLabel("Негативный промпт:") - self.neg_prompt_input = QTextEdit() - self.neg_prompt_input.setPlaceholderText("Введите нежелательные элементы...") - settings_layout.addWidget(neg_prompt_label) - settings_layout.addWidget(self.neg_prompt_input) + negative_prompt_layout = QVBoxLayout() + negative_prompt_label = QLabel("Негативный промпт:") + self.negative_prompt_input = QTextEdit() + self.negative_prompt_input.setMinimumHeight(100) + negative_prompt_layout.addWidget(negative_prompt_label) + negative_prompt_layout.addWidget(self.negative_prompt_input) + settings_layout.addLayout(negative_prompt_layout) # Добавляем менеджер моделей self.model_manager = ModelManager(self.api_url) @@ -316,12 +321,28 @@ class MainWindow(QMainWindow): else: self.prompt_window.close() - def add_prompt(self, prompt): + def toggle_negative_prompt_window(self): + """Открыть/закрыть окно с негативными промптами""" + if self.negative_prompt_window is None: + self.negative_prompt_window = NegativePromptWindow(parent=self) + self.negative_prompt_window.prompt_selected.connect(self.add_negative_prompt) + self.negative_prompt_window.show() + else: + self.negative_prompt_window.close() + + def add_prompt(self, text): """Добавить промпт в поле ввода""" current_text = self.prompt_input.toPlainText() if current_text: current_text += ", " - self.prompt_input.setText(current_text + prompt) + self.prompt_input.setText(current_text + text) + + def add_negative_prompt(self, text): + """Добавить негативный промпт в поле ввода""" + current_text = self.negative_prompt_input.toPlainText() + if current_text: + current_text += ", " + self.negative_prompt_input.setText(current_text + text) def start_generation(self): self.generate_button.setEnabled(False) @@ -329,7 +350,7 @@ class MainWindow(QMainWindow): params = { "prompt": self.prompt_input.toPlainText(), - "negative_prompt": self.neg_prompt_input.toPlainText(), + "negative_prompt": self.negative_prompt_input.toPlainText(), "width": self.width_input.value(), "height": self.height_input.value(), "seed": self.seed_input.value(), diff --git a/modules/negative_prompt_categories.py b/modules/negative_prompt_categories.py new file mode 100644 index 0000000..40275e4 --- /dev/null +++ b/modules/negative_prompt_categories.py @@ -0,0 +1,134 @@ +NEGATIVE_PROMPT_CATEGORIES = { + "Общие проблемы": { + "Качество": [ + "Низкое качество", + "Обычное качество", + "Худшее качество", + "Низкое разрешение", + "Размытое", + "Не в фокусе" + ], + "Технические артефакты": [ + "Логотип", + "Текст", + "Подпись", + "Водяной знак", + "Обрезанное", + "Монохромное", + "Оттенки серого" + ], + "Контент": [ + "Нецензурный контент", + "NSFW", + "Удалить взрослых", + "Удалить молодых" + ] + }, + "Анатомия": { + "Общие проблемы": [ + "Неправильная анатомия", + "Плохое тело", + "Длинное тело", + "Длинная шея", + "Неправильные пропорции", + "Деформированный", + "Мутировавший", + "Мутация", + "Уродливый", + "Испорченный" + ], + "Лицо": [ + "Плохо нарисованное лицо", + "Пятна на коже", + "Веснушки", + "Прыщи", + "Косоглазый" + ], + "Конечности": [ + "Отсутствующая конечность", + "Деформированные конечности", + "Плавающие конечности", + "Отсоединенные конечности", + "Дополнительная конечность", + "Дополнительные руки", + "Мутировавшие руки", + "Плохо нарисованные руки", + "Деформированные руки", + "Мутировавшие руки и пальцы", + "Плохие руки", + "Отсутствующие пальцы", + "Сросшиеся пальцы", + "Слишком много пальцев", + "Дополнительные ноги", + "Плохие ноги" + ] + }, + "Дополнительные модели": { + "Негативные модели": [ + "AS-YoungV2-neg", + "BadDream", + "badhandv4", + "BadNegAnatomyV1-neg", + "EasyNegative", + "FastNegativeV2" + ] + } +} + +# Словарь для перевода русских названий в английские +NEGATIVE_PROMPT_TRANSLATIONS = { + "Низкое качество": "low quality", + "Обычное качество": "normal quality", + "Худшее качество": "worstquality", + "Низкое разрешение": "lowres", + "Размытое": "blurry", + "Не в фокусе": "out of focus", + "Логотип": "logo", + "Текст": "text", + "Подпись": "signature", + "Водяной знак": "watermark", + "Обрезанное": "cropped", + "Монохромное": "monochrome", + "Оттенки серого": "grayscale", + "Нецензурный контент": "NSFW content", + "NSFW": "NSFW", + "Удалить взрослых": "(AS-YoungV2-Neg:1.3)", + "Удалить молодых": "(AS-Adult-Neg:1.3)", + "Неправильная анатомия": "bad anatomy", + "Плохое тело": "bad body", + "Длинное тело": "long body", + "Длинная шея": "long neck", + "Неправильные пропорции": "bad proportions", + "Деформированный": "deformed", + "Мутировавший": "mutated", + "Мутация": "mutation", + "Уродливый": "ugly", + "Испорченный": "disfigured", + "Плохо нарисованное лицо": "poorly drawn face", + "Пятна на коже": "skin blemishes", + "Веснушки": "skin spots", + "Прыщи": "acnes", + "Косоглазый": "cross-eyed", + "Отсутствующая конечность": "missing limb", + "Деформированные конечности": "malformed limbs", + "Плавающие конечности": "floating limbs", + "Отсоединенные конечности": "disconnected limbs", + "Дополнительная конечность": "extra limb", + "Дополнительные руки": "extra arms", + "Мутировавшие руки": "mutated hands", + "Плохо нарисованные руки": "poorly drawn hands", + "Деформированные руки": "malformed hands", + "Мутировавшие руки и пальцы": "mutated hands and fingers", + "Плохие руки": "bad hands", + "Отсутствующие пальцы": "missing fingers", + "Сросшиеся пальцы": "fused fingers", + "Слишком много пальцев": "too many fingers", + "Дополнительные ноги": "extra legs", + "Плохие ноги": "bad feet", + "AS-YoungV2-neg": "AS-YoungV2-neg", + "BadDream": "BadDream", + "badhandv4": "badhandv4", + "BadNegAnatomyV1-neg": "BadNegAnatomyV1-neg", + "EasyNegative": "EasyNegative", + "FastNegativeV2": "FastNegativeV2" +} \ No newline at end of file diff --git a/modules/negative_prompt_window.py b/modules/negative_prompt_window.py new file mode 100644 index 0000000..38f8fdc --- /dev/null +++ b/modules/negative_prompt_window.py @@ -0,0 +1,200 @@ +from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, + QLineEdit, QLabel, QScrollArea, QWidget, QCheckBox, QFrame) +from PyQt6.QtCore import Qt, pyqtSignal +from .negative_prompt_categories import NEGATIVE_PROMPT_CATEGORIES, NEGATIVE_PROMPT_TRANSLATIONS + +class NegativePromptWindow(QDialog): + prompt_selected = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Негативные промпты") + self.setModal(False) + self.selected_prompts = set() + self.setup_ui() + self.load_categories() + + def setup_ui(self): + self.setStyleSheet(""" + QDialog { + background-color: #2b2b2b; + min-width: 1500px; + min-height: 700px; + } + QScrollArea { + background-color: #1e1e1e; + border: 1px solid #3b3b3b; + border-radius: 4px; + padding: 8px; + } + QScrollArea > QWidget > QWidget { + background-color: #1e1e1e; + } + QLabel { + color: #ffffff; + font-size: 14px; + font-weight: bold; + padding: 8px; + background-color: #3b3b3b; + border-radius: 4px; + } + QCheckBox { + color: #ffffff; + font-size: 14px; + padding: 4px; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + } + QCheckBox::indicator:unchecked { + border: 2px solid #555555; + background-color: #3b3b3b; + border-radius: 3px; + } + QCheckBox::indicator:checked { + border: 2px solid #dc3545; + background-color: #dc3545; + border-radius: 3px; + } + QCheckBox::indicator:hover { + border-color: #bb2d3b; + } + QPushButton { + background-color: #dc3545; + color: white; + border: none; + border-radius: 4px; + padding: 8px 16px; + font-weight: bold; + min-width: 120px; + } + QPushButton:hover { + background-color: #bb2d3b; + } + QPushButton:pressed { + background-color: #a52834; + } + QLineEdit { + background-color: #3b3b3b; + color: white; + border: 1px solid #555555; + border-radius: 4px; + padding: 8px; + font-size: 14px; + min-height: 24px; + } + QFrame { + background-color: #1e1e1e; + border: 1px solid #3b3b3b; + border-radius: 4px; + } + """) + + layout = QVBoxLayout(self) + + # Поиск + search_layout = QHBoxLayout() + search_label = QLabel("Поиск:") + self.search_input = QLineEdit() + self.search_input.textChanged.connect(self.filter_prompts) + search_layout.addWidget(search_label) + search_layout.addWidget(self.search_input) + layout.addLayout(search_layout) + + # Горизонтальная прокрутка для категорий + categories_scroll = QScrollArea() + categories_scroll.setWidgetResizable(True) + categories_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) + categories_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + categories_widget = QWidget() + self.categories_layout = QHBoxLayout(categories_widget) + self.categories_layout.setSpacing(10) + categories_scroll.setWidget(categories_widget) + + layout.addWidget(categories_scroll) + + # Кнопки + buttons_layout = QHBoxLayout() + add_button = QPushButton("Добавить выбранные") + add_button.clicked.connect(self.add_selected) + close_button = QPushButton("Закрыть") + close_button.clicked.connect(self.close) + buttons_layout.addWidget(add_button) + buttons_layout.addWidget(close_button) + layout.addLayout(buttons_layout) + + def load_categories(self): + """Загрузка категорий и промптов""" + for category, subcategories in NEGATIVE_PROMPT_CATEGORIES.items(): + # Создаем фрейм для категории + category_frame = QFrame() + category_layout = QVBoxLayout(category_frame) + category_layout.setSpacing(5) + + # Добавляем заголовок категории + category_label = QLabel(category) + category_layout.addWidget(category_label) + + # Создаем скролл для подкатегорий + subcategories_scroll = QScrollArea() + subcategories_scroll.setWidgetResizable(True) + subcategories_widget = QWidget() + subcategories_layout = QVBoxLayout(subcategories_widget) + + for subcategory, prompts in subcategories.items(): + # Добавляем заголовок подкатегории + subcategory_label = QLabel(subcategory) + subcategories_layout.addWidget(subcategory_label) + + # Добавляем чекбоксы для промптов + for prompt in prompts: + checkbox = QCheckBox(prompt) + checkbox.stateChanged.connect(lambda state, p=prompt: self.on_checkbox_changed(state, p)) + subcategories_layout.addWidget(checkbox) + + subcategories_scroll.setWidget(subcategories_widget) + category_layout.addWidget(subcategories_scroll) + + # Добавляем категорию в горизонтальный layout + self.categories_layout.addWidget(category_frame) + + def on_checkbox_changed(self, state, prompt): + """Обработка изменения состояния чекбокса""" + if state == Qt.CheckState.Checked.value: + self.selected_prompts.add(prompt) + else: + self.selected_prompts.discard(prompt) + + def filter_prompts(self, text): + """Фильтрация промптов по поисковому запросу""" + text = text.lower() + for i in range(self.categories_layout.count()): + category_frame = self.categories_layout.itemAt(i).widget() + for j in range(category_frame.layout().count()): + widget = category_frame.layout().itemAt(j).widget() + if isinstance(widget, QScrollArea): + scroll_widget = widget.widget() + for k in range(scroll_widget.layout().count()): + sub_widget = scroll_widget.layout().itemAt(k).widget() + if isinstance(sub_widget, QCheckBox): + sub_widget.setVisible(text in sub_widget.text().lower()) + elif isinstance(sub_widget, QLabel): + # Показываем/скрываем заголовок подкатегории в зависимости от видимости его промптов + has_visible_prompts = False + for l in range(k + 1, scroll_widget.layout().count()): + next_widget = scroll_widget.layout().itemAt(l).widget() + if isinstance(next_widget, QCheckBox) and next_widget.isVisible(): + has_visible_prompts = True + break + sub_widget.setVisible(has_visible_prompts) + + def add_selected(self): + """Добавление выбранных промптов""" + if self.selected_prompts: + # Преобразуем русские названия в английские + translated_prompts = [NEGATIVE_PROMPT_TRANSLATIONS.get(prompt, prompt) for prompt in self.selected_prompts] + prompts_text = ", ".join(sorted(translated_prompts)) + self.prompt_selected.emit(prompts_text) \ No newline at end of file