Воркед
This commit is contained in:
1 parent
ac32c55ab9
commit
ee94d8e529
1138 files changed
+55771
-77901
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
from enum import Enum, auto
|
||||
from typing import List, Dict
|
||||
|
||||
class NegativePromptCategory(Enum):
|
||||
"""Категории негативных промптов"""
|
||||
QUALITY = auto()
|
||||
ARTIFACTS = auto()
|
||||
COMPOSITION = auto()
|
||||
ANATOMY = auto()
|
||||
STYLE = auto()
|
||||
|
||||
class NegativePromptData:
|
||||
"""Класс для хранения данных о негативном промпте"""
|
||||
def __init__(self, text: str, description: str = "", weight: float = 1.0):
|
||||
self.text = text
|
||||
self.description = description
|
||||
self.weight = weight
|
||||
|
||||
class NegativePromptCategories:
|
||||
"""Класс для работы с категориями негативных промптов"""
|
||||
|
||||
@staticmethod
|
||||
def get_prompts() -> Dict[NegativePromptCategory, List[NegativePromptData]]:
|
||||
"""Возвращает словарь с негативными промптами по категориям"""
|
||||
return {
|
||||
NegativePromptCategory.QUALITY: [
|
||||
NegativePromptData("low quality", "Низкое качество"),
|
||||
NegativePromptData("blurry", "Размытое изображение"),
|
||||
NegativePromptData("pixelated", "Пикселизация"),
|
||||
NegativePromptData("jpeg artifacts", "JPEG артефакты"),
|
||||
NegativePromptData("compression artifacts", "Артефакты сжатия"),
|
||||
],
|
||||
NegativePromptCategory.ARTIFACTS: [
|
||||
NegativePromptData("watermark", "Водяные знаки"),
|
||||
NegativePromptData("signature", "Подписи"),
|
||||
NegativePromptData("text", "Текст на изображении"),
|
||||
NegativePromptData("frame", "Рамки"),
|
||||
NegativePromptData("duplicate", "Дубликаты элементов"),
|
||||
],
|
||||
NegativePromptCategory.COMPOSITION: [
|
||||
NegativePromptData("bad composition", "Плохая композиция"),
|
||||
NegativePromptData("unbalanced", "Несбалансированная композиция"),
|
||||
NegativePromptData("cropped", "Обрезанное изображение"),
|
||||
NegativePromptData("out of frame", "Выход за пределы кадра"),
|
||||
],
|
||||
NegativePromptCategory.ANATOMY: [
|
||||
NegativePromptData("bad anatomy", "Неправильная анатомия"),
|
||||
NegativePromptData("extra limbs", "Лишние конечности"),
|
||||
NegativePromptData("missing limbs", "Отсутствующие конечности"),
|
||||
NegativePromptData("deformed", "Деформации"),
|
||||
NegativePromptData("mutated", "Мутации"),
|
||||
],
|
||||
NegativePromptCategory.STYLE: [
|
||||
NegativePromptData("3d render", "3D рендер"),
|
||||
NegativePromptData("cartoon", "Мультяшный стиль"),
|
||||
NegativePromptData("painting", "Живопись"),
|
||||
NegativePromptData("sketch", "Набросок"),
|
||||
NegativePromptData("anime", "Аниме"),
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_category_name(category: NegativePromptCategory) -> str:
|
||||
"""Возвращает название категории на русском"""
|
||||
return {
|
||||
NegativePromptCategory.QUALITY: "Качество",
|
||||
NegativePromptCategory.ARTIFACTS: "Артефакты",
|
||||
NegativePromptCategory.COMPOSITION: "Композиция",
|
||||
NegativePromptCategory.ANATOMY: "Анатомия",
|
||||
NegativePromptCategory.STYLE: "Стиль",
|
||||
}[category]
|
||||
@@ -0,0 +1,86 @@
|
||||
from enum import Enum, auto
|
||||
from typing import List, Dict
|
||||
|
||||
class PromptCategory(Enum):
|
||||
"""Категории промптов"""
|
||||
STYLE = auto()
|
||||
LIGHTING = auto()
|
||||
COMPOSITION = auto()
|
||||
CAMERA = auto()
|
||||
COLORS = auto()
|
||||
MOOD = auto()
|
||||
QUALITY = auto()
|
||||
|
||||
class PromptData:
|
||||
"""Класс для хранения данных о промпте"""
|
||||
def __init__(self, text: str, description: str = "", weight: float = 1.0):
|
||||
self.text = text
|
||||
self.description = description
|
||||
self.weight = weight
|
||||
|
||||
class PromptCategories:
|
||||
"""Класс для работы с категориями промптов"""
|
||||
|
||||
@staticmethod
|
||||
def get_prompts() -> Dict[PromptCategory, List[PromptData]]:
|
||||
"""Возвращает словарь с промптами по категориям"""
|
||||
return {
|
||||
PromptCategory.STYLE: [
|
||||
PromptData("digital art", "Цифровое искусство"),
|
||||
PromptData("oil painting", "Масляная живопись"),
|
||||
PromptData("watercolor", "Акварель"),
|
||||
PromptData("pencil sketch", "Карандашный набросок"),
|
||||
PromptData("anime style", "Аниме стиль"),
|
||||
PromptData("photorealistic", "Фотореалистичный"),
|
||||
],
|
||||
PromptCategory.LIGHTING: [
|
||||
PromptData("soft lighting", "Мягкое освещение"),
|
||||
PromptData("dramatic lighting", "Драматическое освещение"),
|
||||
PromptData("cinematic lighting", "Кинематографическое освещение"),
|
||||
PromptData("natural light", "Естественное освещение"),
|
||||
PromptData("studio lighting", "Студийное освещение"),
|
||||
],
|
||||
PromptCategory.COMPOSITION: [
|
||||
PromptData("rule of thirds", "Правило третей"),
|
||||
PromptData("symmetrical", "Симметричная композиция"),
|
||||
PromptData("centered composition", "Центральная композиция"),
|
||||
PromptData("dynamic pose", "Динамичная поза"),
|
||||
],
|
||||
PromptCategory.CAMERA: [
|
||||
PromptData("close-up shot", "Крупный план"),
|
||||
PromptData("wide angle", "Широкий угол"),
|
||||
PromptData("bokeh", "Боке"),
|
||||
PromptData("depth of field", "Глубина резкости"),
|
||||
],
|
||||
PromptCategory.COLORS: [
|
||||
PromptData("vibrant colors", "Яркие цвета"),
|
||||
PromptData("pastel colors", "Пастельные цвета"),
|
||||
PromptData("monochrome", "Монохром"),
|
||||
PromptData("high contrast", "Высокий контраст"),
|
||||
],
|
||||
PromptCategory.MOOD: [
|
||||
PromptData("peaceful", "Умиротворенное"),
|
||||
PromptData("mysterious", "Таинственное"),
|
||||
PromptData("energetic", "Энергичное"),
|
||||
PromptData("melancholic", "Меланхоличное"),
|
||||
],
|
||||
PromptCategory.QUALITY: [
|
||||
PromptData("highly detailed", "Высокая детализация"),
|
||||
PromptData("8k uhd", "Высокое разрешение"),
|
||||
PromptData("masterpiece", "Шедевр"),
|
||||
PromptData("best quality", "Лучшее качество"),
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_category_name(category: PromptCategory) -> str:
|
||||
"""Возвращает название категории на русском"""
|
||||
return {
|
||||
PromptCategory.STYLE: "Стили",
|
||||
PromptCategory.LIGHTING: "Освещение",
|
||||
PromptCategory.COMPOSITION: "Композиция",
|
||||
PromptCategory.CAMERA: "Камера",
|
||||
PromptCategory.COLORS: "Цвета",
|
||||
PromptCategory.MOOD: "Настроение",
|
||||
PromptCategory.QUALITY: "Качество",
|
||||
}[category]
|
||||
@@ -0,0 +1,86 @@
|
||||
from typing import Dict
|
||||
|
||||
class PromptTranslations:
|
||||
"""Класс для работы с переводами промптов"""
|
||||
|
||||
@staticmethod
|
||||
def get_translations() -> Dict[str, str]:
|
||||
"""Возвращает словарь с переводами промптов"""
|
||||
return {
|
||||
# Стили
|
||||
"digital art": "цифровое искусство",
|
||||
"oil painting": "масляная живопись",
|
||||
"watercolor": "акварель",
|
||||
"pencil sketch": "карандашный набросок",
|
||||
"anime style": "аниме стиль",
|
||||
"photorealistic": "фотореалистичный",
|
||||
|
||||
# Освещение
|
||||
"soft lighting": "мягкое освещение",
|
||||
"dramatic lighting": "драматическое освещение",
|
||||
"cinematic lighting": "кинематографическое освещение",
|
||||
"natural light": "естественное освещение",
|
||||
"studio lighting": "студийное освещение",
|
||||
|
||||
# Композиция
|
||||
"rule of thirds": "правило третей",
|
||||
"symmetrical": "симметричный",
|
||||
"centered composition": "центральная композиция",
|
||||
"dynamic pose": "динамичная поза",
|
||||
|
||||
# Камера
|
||||
"close-up shot": "крупный план",
|
||||
"wide angle": "широкий угол",
|
||||
"bokeh": "боке",
|
||||
"depth of field": "глубина резкости",
|
||||
|
||||
# Цвета
|
||||
"vibrant colors": "яркие цвета",
|
||||
"pastel colors": "пастельные цвета",
|
||||
"monochrome": "монохром",
|
||||
"high contrast": "высокий контраст",
|
||||
|
||||
# Настроение
|
||||
"peaceful": "умиротворенный",
|
||||
"mysterious": "таинственный",
|
||||
"energetic": "энергичный",
|
||||
"melancholic": "меланхоличный",
|
||||
|
||||
# Качество
|
||||
"highly detailed": "высокая детализация",
|
||||
"8k uhd": "8k uhd",
|
||||
"masterpiece": "шедевр",
|
||||
"best quality": "лучшее качество",
|
||||
|
||||
# Негативные промпты
|
||||
"low quality": "низкое качество",
|
||||
"blurry": "размытый",
|
||||
"pixelated": "пикселизированный",
|
||||
"jpeg artifacts": "jpeg артефакты",
|
||||
"compression artifacts": "артефакты сжатия",
|
||||
"watermark": "водяной знак",
|
||||
"signature": "подпись",
|
||||
"text": "текст",
|
||||
"frame": "рамка",
|
||||
"duplicate": "дубликат",
|
||||
"bad composition": "плохая композиция",
|
||||
"unbalanced": "несбалансированный",
|
||||
"cropped": "обрезанный",
|
||||
"out of frame": "за пределами кадра",
|
||||
"bad anatomy": "плохая анатомия",
|
||||
"extra limbs": "лишние конечности",
|
||||
"missing limbs": "отсутствующие конечности",
|
||||
"deformed": "деформированный",
|
||||
"mutated": "мутированный",
|
||||
"3d render": "3d рендер",
|
||||
"cartoon": "мультяшный",
|
||||
"painting": "живопись",
|
||||
"sketch": "набросок",
|
||||
"anime": "аниме",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def translate(text: str) -> str:
|
||||
"""Переводит промпт на русский язык"""
|
||||
translations = PromptTranslations.get_translations()
|
||||
return translations.get(text.lower(), text)
|
||||
+82
-16
@@ -2,6 +2,17 @@ from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QComboBox, QPushButton, QLabel)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
import requests
|
||||
from typing import List, Dict
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загружаем переменные окружения
|
||||
load_dotenv()
|
||||
|
||||
class Model:
|
||||
def __init__(self, name: str, title: str = None):
|
||||
self.name = name
|
||||
self.title = title or name
|
||||
|
||||
class ModelManager(QWidget):
|
||||
model_changed = pyqtSignal(str)
|
||||
@@ -9,6 +20,8 @@ class ModelManager(QWidget):
|
||||
def __init__(self, api_url, parent=None):
|
||||
super().__init__(parent)
|
||||
self.api_url = api_url
|
||||
self.models = []
|
||||
self.current_model = None
|
||||
self.setup_ui()
|
||||
self.load_models()
|
||||
|
||||
@@ -97,33 +110,86 @@ class ModelManager(QWidget):
|
||||
try:
|
||||
response = requests.get(f"{self.api_url}/sdapi/v1/sd-models")
|
||||
if response.status_code == 200:
|
||||
models = response.json()
|
||||
current_model = self.model_combo.currentText()
|
||||
models_data = response.json()
|
||||
self.models = [
|
||||
Model(model.get('title', ''), model.get('model_name', ''))
|
||||
for model in models_data
|
||||
]
|
||||
|
||||
# Получаем текущую модель
|
||||
options_response = requests.get(f"{self.api_url}/sdapi/v1/options")
|
||||
if options_response.status_code == 200:
|
||||
current_model_name = options_response.json().get('sd_model_checkpoint')
|
||||
self.current_model = next(
|
||||
(model for model in self.models if model.name == current_model_name),
|
||||
self.models[0] if self.models else None
|
||||
)
|
||||
|
||||
# Обновляем UI
|
||||
self.model_combo.clear()
|
||||
for model in models:
|
||||
self.model_combo.addItem(model.get('title', ''))
|
||||
for model in self.models:
|
||||
self.model_combo.addItem(model.title)
|
||||
|
||||
# Устанавливаем текущую модель
|
||||
if self.current_model:
|
||||
index = self.model_combo.findText(self.current_model.title)
|
||||
if index >= 0:
|
||||
self.model_combo.setCurrentIndex(index)
|
||||
|
||||
# Восстанавливаем выбранную модель, если она всё ещё доступна
|
||||
index = self.model_combo.findText(current_model)
|
||||
if index >= 0:
|
||||
self.model_combo.setCurrentIndex(index)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке моделей: {str(e)}")
|
||||
self.models = []
|
||||
self.current_model = None
|
||||
|
||||
def on_model_changed(self, model_name):
|
||||
def on_model_changed(self, model_title):
|
||||
"""Обработка смены модели"""
|
||||
model = next(
|
||||
(m for m in self.models if m.title == model_title),
|
||||
None
|
||||
)
|
||||
if model:
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/sdapi/v1/options",
|
||||
json={"sd_model_checkpoint": model.name}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
self.current_model = model
|
||||
self.model_changed.emit(model.name)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при смене модели: {str(e)}")
|
||||
|
||||
def update_api_url(self, new_url):
|
||||
"""Обновить URL API"""
|
||||
self.api_url = new_url
|
||||
self.load_models()
|
||||
|
||||
def get_models(self):
|
||||
"""Возвращает список доступных моделей"""
|
||||
return self.models
|
||||
|
||||
def get_model_names(self):
|
||||
"""Возвращает список имен моделей"""
|
||||
return [model.name for model in self.models]
|
||||
|
||||
def get_current_model(self):
|
||||
"""Возвращает текущую модель"""
|
||||
return self.current_model
|
||||
|
||||
def switch_model(self, model_name: str) -> bool:
|
||||
"""Переключение на другую модель"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/sdapi/v1/options",
|
||||
json={"sd_model_checkpoint": model_name}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
self.model_changed.emit(model_name)
|
||||
self.current_model = next(
|
||||
(model for model in self.models if model.name == model_name),
|
||||
None
|
||||
)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Ошибка при смене модели: {str(e)}")
|
||||
|
||||
def update_api_url(self, new_url):
|
||||
"""Обновить URL API"""
|
||||
self.api_url = new_url
|
||||
self.load_models()
|
||||
print(f"Ошибка переключения модели: {str(e)}")
|
||||
return False
|
||||
Binary file not shown.
Binary file not shown.
+210
-168
@@ -1,195 +1,237 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QLineEdit, QTextEdit,
|
||||
QSpinBox, QDoubleSpinBox, QComboBox)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QLineEdit, QSpinBox,
|
||||
QComboBox, QProgressBar)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QPixmap
|
||||
import requests
|
||||
from ..styles.style_constants import StyleConstants
|
||||
from ..styles.icons import IconManager, IconType
|
||||
from ..styles.page import BasePage
|
||||
from ..styles.theme import Colors, Spacing, Fonts
|
||||
from ..styles.theme_manager import ThemeManager
|
||||
|
||||
class HomePage(BasePage):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, model_manager, sampler_manager, size_preset_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.theme_manager = ThemeManager.get_instance()
|
||||
self.setup_content()
|
||||
self.model_manager = model_manager
|
||||
self.sampler_manager = sampler_manager
|
||||
self.size_preset_manager = size_preset_manager
|
||||
self.icon_manager = IconManager.get_instance()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_content(self):
|
||||
"""Настройка содержимого страницы"""
|
||||
colors = self.theme_manager.current_theme.get_colors()
|
||||
spacing = self.theme_manager.current_theme.get_spacing()
|
||||
def setup_ui(self):
|
||||
# Верхняя панель с настройками
|
||||
settings_panel = QHBoxLayout()
|
||||
settings_panel.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
settings_panel.setSpacing(StyleConstants.SPACING['md'])
|
||||
|
||||
# Заголовок
|
||||
header = QLabel("Генерация изображений")
|
||||
header.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
""")
|
||||
self.layout.addWidget(header)
|
||||
# Выбор модели
|
||||
model_label = QLabel("Модель:")
|
||||
settings_panel.addWidget(model_label)
|
||||
|
||||
# Промпт
|
||||
prompt_layout = QVBoxLayout()
|
||||
self.model_combo = QComboBox()
|
||||
self.model_combo.currentTextChanged.connect(self.on_model_changed)
|
||||
settings_panel.addWidget(self.model_combo)
|
||||
|
||||
# Выбор сэмплера
|
||||
sampler_label = QLabel("Сэмплер:")
|
||||
settings_panel.addWidget(sampler_label)
|
||||
|
||||
self.sampler_combo = QComboBox()
|
||||
self.sampler_combo.currentTextChanged.connect(self.on_sampler_changed)
|
||||
settings_panel.addWidget(self.sampler_combo)
|
||||
|
||||
# Настройки размера
|
||||
size_label = QLabel("Размер:")
|
||||
settings_panel.addWidget(size_label)
|
||||
|
||||
self.width_spin = QSpinBox()
|
||||
self.width_spin.setRange(64, 2048)
|
||||
self.width_spin.setSingleStep(64)
|
||||
self.width_spin.setValue(512)
|
||||
settings_panel.addWidget(self.width_spin)
|
||||
|
||||
self.height_spin = QSpinBox()
|
||||
self.height_spin.setRange(64, 2048)
|
||||
self.height_spin.setSingleStep(64)
|
||||
self.height_spin.setValue(512)
|
||||
settings_panel.addWidget(self.height_spin)
|
||||
|
||||
settings_panel.addStretch()
|
||||
self.layout.addLayout(settings_panel)
|
||||
|
||||
# Панель промптов
|
||||
prompts_panel = QVBoxLayout()
|
||||
prompts_panel.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
0,
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
prompts_panel.setSpacing(StyleConstants.SPACING['md'])
|
||||
|
||||
# Позитивный промпт
|
||||
prompt_label = QLabel("Промпт:")
|
||||
prompt_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.prompt_input = QTextEdit()
|
||||
self.prompt_input.setPlaceholderText("Опишите изображение, которое хотите сгенерировать...")
|
||||
self.prompt_input.setMinimumHeight(100)
|
||||
prompts_panel.addWidget(prompt_label)
|
||||
|
||||
prompt_layout.addWidget(prompt_label)
|
||||
prompt_layout.addWidget(self.prompt_input)
|
||||
self.layout.addLayout(prompt_layout)
|
||||
self.prompt_edit = QLineEdit()
|
||||
self.prompt_edit.setPlaceholderText("Опишите желаемое изображение...")
|
||||
prompts_panel.addWidget(self.prompt_edit)
|
||||
|
||||
# Негативный промпт
|
||||
negative_prompt_layout = QVBoxLayout()
|
||||
negative_prompt_label = QLabel("Негативный промпт:")
|
||||
negative_prompt_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.negative_prompt_input = QTextEdit()
|
||||
self.negative_prompt_input.setPlaceholderText("Опишите, что не должно появиться на изображении...")
|
||||
self.negative_prompt_input.setMinimumHeight(100)
|
||||
prompts_panel.addWidget(negative_prompt_label)
|
||||
|
||||
negative_prompt_layout.addWidget(negative_prompt_label)
|
||||
negative_prompt_layout.addWidget(self.negative_prompt_input)
|
||||
self.layout.addLayout(negative_prompt_layout)
|
||||
self.negative_prompt_edit = QLineEdit()
|
||||
self.negative_prompt_edit.setPlaceholderText("Опишите нежелательные элементы...")
|
||||
prompts_panel.addWidget(self.negative_prompt_edit)
|
||||
|
||||
# Параметры генерации
|
||||
params_layout = QHBoxLayout()
|
||||
self.layout.addLayout(prompts_panel)
|
||||
|
||||
# Размер изображения
|
||||
size_layout = QVBoxLayout()
|
||||
size_label = QLabel("Размер:")
|
||||
size_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.size_combo = QComboBox()
|
||||
self.size_combo.addItems(["512x512", "768x768", "1024x1024"])
|
||||
size_layout.addWidget(size_label)
|
||||
size_layout.addWidget(self.size_combo)
|
||||
params_layout.addLayout(size_layout)
|
||||
# Панель генерации
|
||||
generate_panel = QHBoxLayout()
|
||||
generate_panel.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
0,
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
generate_panel.setSpacing(StyleConstants.SPACING['md'])
|
||||
|
||||
# Количество шагов
|
||||
steps_layout = QVBoxLayout()
|
||||
steps_label = QLabel("Шаги:")
|
||||
steps_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.steps_spin = QSpinBox()
|
||||
self.steps_spin.setRange(1, 150)
|
||||
self.steps_spin.setValue(20)
|
||||
steps_layout.addWidget(steps_label)
|
||||
steps_layout.addWidget(self.steps_spin)
|
||||
params_layout.addLayout(steps_layout)
|
||||
self.steps_spin.setPrefix("Шаги: ")
|
||||
generate_panel.addWidget(self.steps_spin)
|
||||
|
||||
# CFG Scale
|
||||
cfg_layout = QVBoxLayout()
|
||||
cfg_label = QLabel("CFG Scale:")
|
||||
cfg_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.cfg_spin = QDoubleSpinBox()
|
||||
self.cfg_spin.setRange(1.0, 30.0)
|
||||
self.cfg_spin.setValue(7.0)
|
||||
self.cfg_spin.setSingleStep(0.5)
|
||||
cfg_layout.addWidget(cfg_label)
|
||||
cfg_layout.addWidget(self.cfg_spin)
|
||||
params_layout.addLayout(cfg_layout)
|
||||
self.cfg_spin = QSpinBox()
|
||||
self.cfg_spin.setRange(1, 30)
|
||||
self.cfg_spin.setValue(7)
|
||||
self.cfg_spin.setPrefix("CFG: ")
|
||||
generate_panel.addWidget(self.cfg_spin)
|
||||
|
||||
# Сэмплер
|
||||
sampler_layout = QVBoxLayout()
|
||||
sampler_label = QLabel("Сэмплер:")
|
||||
sampler_label.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
""")
|
||||
self.sampler_combo = QComboBox()
|
||||
self.sampler_combo.addItems(["Euler a", "Euler", "DPM++ 2M Karras"])
|
||||
sampler_layout.addWidget(sampler_label)
|
||||
sampler_layout.addWidget(self.sampler_combo)
|
||||
params_layout.addLayout(sampler_layout)
|
||||
self.batch_size_spin = QSpinBox()
|
||||
self.batch_size_spin.setRange(1, 4)
|
||||
self.batch_size_spin.setValue(1)
|
||||
self.batch_size_spin.setPrefix("Количество: ")
|
||||
generate_panel.addWidget(self.batch_size_spin)
|
||||
|
||||
# Добавляем параметры на страницу
|
||||
self.layout.addLayout(params_layout)
|
||||
self.generate_btn = QPushButton("Сгенерировать")
|
||||
self.generate_btn.setIcon(self.icon_manager.get_icon(IconType.GENERATE))
|
||||
self.generate_btn.clicked.connect(self.generate_images)
|
||||
generate_panel.addWidget(self.generate_btn)
|
||||
|
||||
# Кнопки управления
|
||||
buttons_layout = QHBoxLayout()
|
||||
buttons_layout.setSpacing(spacing.MEDIUM)
|
||||
generate_panel.addStretch()
|
||||
self.layout.addLayout(generate_panel)
|
||||
|
||||
self.generate_button = QPushButton("Сгенерировать")
|
||||
self.generate_button.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
min-width: 150px;
|
||||
background-color: {colors['primary']};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px {spacing.MEDIUM}px;
|
||||
}}
|
||||
# Прогресс-бар
|
||||
progress_layout = QHBoxLayout()
|
||||
progress_layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
0,
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setTextVisible(True)
|
||||
self.progress_bar.setFormat("%p% - %v/%m шагов")
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
|
||||
self.layout.addLayout(progress_layout)
|
||||
|
||||
# Область для результатов
|
||||
result_layout = QVBoxLayout()
|
||||
result_layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
0,
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
|
||||
self.result_label = QLabel()
|
||||
self.result_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
result_layout.addWidget(self.result_label)
|
||||
|
||||
self.layout.addLayout(result_layout)
|
||||
self.layout.addStretch()
|
||||
|
||||
# Обновляем UI
|
||||
self.update_ui()
|
||||
|
||||
def update_ui(self):
|
||||
"""Обновление UI после загрузки данных"""
|
||||
# Обновляем список моделей
|
||||
self.model_combo.clear()
|
||||
for model in self.model_manager.get_models():
|
||||
self.model_combo.addItem(model.title, model.name)
|
||||
|
||||
# Устанавливаем текущую модель
|
||||
current_model = self.model_manager.get_current_model()
|
||||
if current_model:
|
||||
index = self.model_combo.findData(current_model.name)
|
||||
if index >= 0:
|
||||
self.model_combo.setCurrentIndex(index)
|
||||
|
||||
# Обновляем список сэмплеров
|
||||
self.sampler_combo.clear()
|
||||
for sampler in self.sampler_manager.get_samplers():
|
||||
self.sampler_combo.addItem(sampler.title, sampler.name)
|
||||
|
||||
# Устанавливаем текущий сэмплер
|
||||
current_sampler = self.sampler_manager.get_current_sampler()
|
||||
if current_sampler:
|
||||
index = self.sampler_combo.findData(current_sampler.name)
|
||||
if index >= 0:
|
||||
self.sampler_combo.setCurrentIndex(index)
|
||||
|
||||
def on_model_changed(self, model_title):
|
||||
"""Обработка изменения модели"""
|
||||
model_name = self.model_combo.currentData()
|
||||
if model_name:
|
||||
self.model_manager.switch_model(model_name)
|
||||
|
||||
def on_sampler_changed(self, sampler_title):
|
||||
"""Обработка изменения сэмплера"""
|
||||
sampler_name = self.sampler_combo.currentData()
|
||||
if sampler_name:
|
||||
self.sampler_manager.set_current_sampler(sampler_name)
|
||||
|
||||
def set_resolution(self, width: int, height: int):
|
||||
"""Установка разрешения"""
|
||||
self.width_spin.setValue(width)
|
||||
self.height_spin.setValue(height)
|
||||
|
||||
def generate_images(self):
|
||||
"""Генерация изображений"""
|
||||
try:
|
||||
# Получаем параметры
|
||||
params = {
|
||||
"prompt": self.prompt_edit.text(),
|
||||
"negative_prompt": self.negative_prompt_edit.text(),
|
||||
"steps": self.steps_spin.value(),
|
||||
"cfg_scale": self.cfg_spin.value(),
|
||||
"batch_size": self.batch_size_spin.value(),
|
||||
"width": self.width_spin.value(),
|
||||
"height": self.height_spin.value(),
|
||||
"sampler_name": self.sampler_combo.currentData(),
|
||||
}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {colors['hover']};
|
||||
}}
|
||||
# Отправляем запрос
|
||||
response = requests.post(
|
||||
f"{self.model_manager.api_url}/sdapi/v1/txt2img",
|
||||
json=params
|
||||
)
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {colors['active']};
|
||||
}}
|
||||
""")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
# TODO: Отобразить сгенерированные изображения
|
||||
print("Изображения успешно сгенерированы")
|
||||
else:
|
||||
print(f"Ошибка генерации: {response.status_code}")
|
||||
|
||||
self.clear_button = QPushButton("Очистить")
|
||||
self.clear_button.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
min-width: 150px;
|
||||
background-color: transparent;
|
||||
color: {colors['primary']};
|
||||
border: 1px solid {colors['primary']};
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px {spacing.MEDIUM}px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {colors['hover']};
|
||||
color: white;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {colors['active']};
|
||||
color: white;
|
||||
border: none;
|
||||
}}
|
||||
""")
|
||||
|
||||
buttons_layout.addWidget(self.generate_button)
|
||||
buttons_layout.addWidget(self.clear_button)
|
||||
buttons_layout.addStretch()
|
||||
|
||||
self.layout.addLayout(buttons_layout)
|
||||
|
||||
# Растягиваем пространство
|
||||
self.layout.addStretch()
|
||||
except Exception as e:
|
||||
print(f"Ошибка при генерации: {str(e)}")
|
||||
+124
-157
@@ -1,178 +1,145 @@
|
||||
from PyQt6.QtWidgets import (QLabel, QPushButton, QHBoxLayout,
|
||||
QVBoxLayout, QLineEdit, QListWidget,
|
||||
QListWidgetItem, QFrame)
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QLineEdit, QSpinBox,
|
||||
QComboBox, QProgressBar, QScrollArea, QFrame)
|
||||
from PyQt6.QtCore import Qt
|
||||
from modules.styles.page import BasePage
|
||||
from modules.styles.theme import Colors
|
||||
from modules.styles.theme_manager import ThemeManager
|
||||
from ..styles.style_constants import StyleConstants
|
||||
from ..styles.icons import IconManager, IconType
|
||||
from ..styles.page import BasePage
|
||||
|
||||
class ModelItem(QFrame):
|
||||
def __init__(self, model_name: str, parent=None):
|
||||
class ModelCard(QFrame):
|
||||
"""Карточка модели"""
|
||||
def __init__(self, model, parent=None):
|
||||
super().__init__(parent)
|
||||
self.model_name = model_name
|
||||
self.theme_manager = ThemeManager.get_instance()
|
||||
self.model = model
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Настройка UI элемента модели"""
|
||||
colors = self.theme_manager.current_theme.get_colors()
|
||||
spacing = self.theme_manager.current_theme.get_spacing()
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(8, 8, 8, 8)
|
||||
|
||||
# Название модели
|
||||
name_label = QLabel(self.model_name)
|
||||
name_label.setStyleSheet(f"color: {colors['text']};")
|
||||
layout.addWidget(name_label)
|
||||
|
||||
# Растягиваем пространство
|
||||
layout.addStretch()
|
||||
|
||||
# Кнопки управления
|
||||
select_btn = QPushButton("Выбрать")
|
||||
select_btn.setMinimumWidth(100)
|
||||
select_btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: {colors['primary']};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px {spacing.MEDIUM}px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {colors['hover']};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {colors['active']};
|
||||
}}
|
||||
""")
|
||||
layout.addWidget(select_btn)
|
||||
|
||||
delete_btn = QPushButton("Удалить")
|
||||
delete_btn.setMinimumWidth(100)
|
||||
delete_btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: {colors['error']};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px {spacing.MEDIUM}px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {colors['hover']};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {colors['active']};
|
||||
}}
|
||||
""")
|
||||
layout.addWidget(delete_btn)
|
||||
|
||||
# Стили
|
||||
self.setStyleSheet(f"""
|
||||
QFrame {{
|
||||
background-color: {colors['surface']};
|
||||
border: 1px solid {colors['border']};
|
||||
border-radius: {spacing.TINY}px;
|
||||
background-color: {StyleConstants.COLORS['dark']['surface']};
|
||||
border-radius: {StyleConstants.RADIUS['md']}px;
|
||||
padding: {StyleConstants.SPACING['md']}px;
|
||||
margin-bottom: {StyleConstants.SPACING['md']}px;
|
||||
}}
|
||||
""")
|
||||
|
||||
class ModelsPage(BasePage):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.theme_manager = ThemeManager.get_instance()
|
||||
self.setup_content()
|
||||
|
||||
def setup_content(self):
|
||||
"""Настройка содержимого страницы"""
|
||||
colors = self.theme_manager.current_theme.get_colors()
|
||||
spacing = self.theme_manager.current_theme.get_spacing()
|
||||
|
||||
# Заголовок
|
||||
header = QLabel("Модели")
|
||||
header.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {colors['text']};
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: {StyleConstants.COLORS['dark']['text']};
|
||||
}}
|
||||
""")
|
||||
self.layout.addWidget(header)
|
||||
|
||||
# Поиск и добавление
|
||||
search_layout = QHBoxLayout()
|
||||
|
||||
self.search_input = QLineEdit()
|
||||
self.search_input.setPlaceholderText("Поиск моделей...")
|
||||
self.search_input.setStyleSheet(f"""
|
||||
QLineEdit {{
|
||||
background-color: {colors['surface']};
|
||||
color: {colors['text']};
|
||||
border: 1px solid {colors['border']};
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px;
|
||||
}}
|
||||
|
||||
QLineEdit:focus {{
|
||||
border: 1px solid {colors['primary']};
|
||||
}}
|
||||
""")
|
||||
search_layout.addWidget(self.search_input)
|
||||
|
||||
self.add_btn = QPushButton("Добавить модель")
|
||||
self.add_btn.setMinimumWidth(150)
|
||||
self.add_btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: {colors['primary']};
|
||||
background-color: {StyleConstants.COLORS['primary']};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: {spacing.TINY}px;
|
||||
padding: {spacing.SMALL}px {spacing.MEDIUM}px;
|
||||
border-radius: {StyleConstants.RADIUS['sm']}px;
|
||||
padding: {StyleConstants.SPACING['sm']}px {StyleConstants.SPACING['md']}px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {colors['hover']};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {colors['active']};
|
||||
}}
|
||||
""")
|
||||
search_layout.addWidget(self.add_btn)
|
||||
|
||||
self.layout.addLayout(search_layout)
|
||||
|
||||
# Список моделей
|
||||
self.models_list = QListWidget()
|
||||
self.models_list.setSpacing(8)
|
||||
self.models_list.setStyleSheet(f"""
|
||||
QListWidget {{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QListWidget::item {{
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
background-color: {StyleConstants.COLORS['secondary']};
|
||||
}}
|
||||
""")
|
||||
|
||||
# Добавляем тестовые модели
|
||||
test_models = [
|
||||
"stable-diffusion-v1-5",
|
||||
"stable-diffusion-v2-1",
|
||||
"stable-diffusion-xl-base-1.0"
|
||||
]
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(StyleConstants.SPACING['sm'])
|
||||
|
||||
for model in test_models:
|
||||
item = QListWidgetItem(self.models_list)
|
||||
model_widget = ModelItem(model)
|
||||
item.setSizeHint(model_widget.sizeHint())
|
||||
self.models_list.addItem(item)
|
||||
self.models_list.setItemWidget(item, model_widget)
|
||||
# Название модели
|
||||
title = QLabel(self.model.title)
|
||||
title.setStyleSheet(f"""
|
||||
font-size: {StyleConstants.FONTS['size']['lg']};
|
||||
font-weight: {StyleConstants.FONTS['weight']['bold']};
|
||||
""")
|
||||
layout.addWidget(title)
|
||||
|
||||
self.layout.addWidget(self.models_list)
|
||||
# Имя файла модели
|
||||
name = QLabel(self.model.name)
|
||||
name.setStyleSheet(f"""
|
||||
font-size: {StyleConstants.FONTS['size']['sm']};
|
||||
color: {StyleConstants.COLORS['dark']['text_secondary']};
|
||||
""")
|
||||
layout.addWidget(name)
|
||||
|
||||
# Кнопки управления
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
select_btn = QPushButton("Выбрать")
|
||||
select_btn.setIcon(IconManager.get_instance().get_icon(IconType.SUCCESS))
|
||||
buttons_layout.addWidget(select_btn)
|
||||
|
||||
delete_btn = QPushButton("Удалить")
|
||||
delete_btn.setIcon(IconManager.get_instance().get_icon(IconType.DELETE))
|
||||
buttons_layout.addWidget(delete_btn)
|
||||
|
||||
buttons_layout.addStretch()
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
class ModelsPage(BasePage):
|
||||
"""Страница управления моделями"""
|
||||
def __init__(self, model_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.model_manager = model_manager
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
# Верхняя панель
|
||||
top_panel = QHBoxLayout()
|
||||
top_panel.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
top_panel.setSpacing(StyleConstants.SPACING['md'])
|
||||
|
||||
# Поиск
|
||||
self.search_edit = QLineEdit()
|
||||
self.search_edit.setPlaceholderText("Поиск моделей...")
|
||||
top_panel.addWidget(self.search_edit)
|
||||
|
||||
# Кнопка обновления
|
||||
refresh_btn = QPushButton("Обновить")
|
||||
refresh_btn.setIcon(IconManager.get_instance().get_icon(IconType.REFRESH))
|
||||
refresh_btn.clicked.connect(self.update_ui)
|
||||
top_panel.addWidget(refresh_btn)
|
||||
|
||||
# Кнопка добавления
|
||||
add_btn = QPushButton("Добавить")
|
||||
add_btn.setIcon(IconManager.get_instance().get_icon(IconType.ADD))
|
||||
top_panel.addWidget(add_btn)
|
||||
|
||||
top_panel.addStretch()
|
||||
self.layout.addLayout(top_panel)
|
||||
|
||||
# Область прокрутки для моделей
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
||||
# Контейнер для карточек моделей
|
||||
self.models_container = QWidget()
|
||||
self.models_layout = QVBoxLayout(self.models_container)
|
||||
self.models_layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
0,
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
self.models_layout.setSpacing(0)
|
||||
|
||||
scroll_area.setWidget(self.models_container)
|
||||
self.layout.addWidget(scroll_area)
|
||||
|
||||
# Обновляем UI
|
||||
self.update_ui()
|
||||
|
||||
def update_ui(self):
|
||||
"""Обновление списка моделей"""
|
||||
# Очищаем контейнер
|
||||
for i in reversed(range(self.models_layout.count())):
|
||||
widget = self.models_layout.itemAt(i).widget()
|
||||
if widget:
|
||||
widget.deleteLater()
|
||||
|
||||
# Добавляем карточки моделей
|
||||
for model in self.model_manager.get_models():
|
||||
card = ModelCard(model)
|
||||
self.models_layout.addWidget(card)
|
||||
|
||||
# Добавляем растягивающийся элемент в конец
|
||||
self.models_layout.addStretch()
|
||||
+46
-19
@@ -2,6 +2,17 @@ from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QComboBox, QLabel)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
import requests
|
||||
from typing import List, Dict
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загружаем переменные окружения
|
||||
load_dotenv()
|
||||
|
||||
class Sampler:
|
||||
def __init__(self, name: str, title: str = None):
|
||||
self.name = name
|
||||
self.title = title or name
|
||||
|
||||
class SamplerManager(QWidget):
|
||||
sampler_changed = pyqtSignal(str)
|
||||
@@ -71,28 +82,25 @@ class SamplerManager(QWidget):
|
||||
layout.addStretch()
|
||||
|
||||
def load_samplers(self):
|
||||
"""Загрузить список доступных сэмплеров"""
|
||||
"""Загрузка списка сэмплеров с сервера"""
|
||||
try:
|
||||
response = requests.get(f"{self.api_url}/sdapi/v1/samplers")
|
||||
if response.status_code == 200:
|
||||
samplers = response.json()
|
||||
current_sampler = self.sampler_combo.currentText()
|
||||
samplers_data = response.json()
|
||||
self.samplers = [
|
||||
Sampler(sampler['name'], sampler.get('title'))
|
||||
for sampler in samplers_data
|
||||
]
|
||||
|
||||
self.sampler_combo.clear()
|
||||
for sampler in samplers:
|
||||
self.sampler_combo.addItem(sampler.get('name', ''))
|
||||
|
||||
# Восстанавливаем выбранный сэмплер, если он всё ещё доступен
|
||||
index = self.sampler_combo.findText(current_sampler)
|
||||
if index >= 0:
|
||||
self.sampler_combo.setCurrentIndex(index)
|
||||
elif self.sampler_combo.count() > 0:
|
||||
# По умолчанию выбираем Euler a
|
||||
default_index = self.sampler_combo.findText("Euler a")
|
||||
if default_index >= 0:
|
||||
self.sampler_combo.setCurrentIndex(default_index)
|
||||
# По умолчанию выбираем Euler a
|
||||
self.current_sampler = next(
|
||||
(sampler for sampler in self.samplers if sampler.name == "Euler a"),
|
||||
self.samplers[0] if self.samplers else None
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке сэмплеров: {str(e)}")
|
||||
print(f"Ошибка загрузки сэмплеров: {str(e)}")
|
||||
self.samplers = []
|
||||
self.current_sampler = None
|
||||
|
||||
def on_sampler_changed(self, sampler_name):
|
||||
"""Обработка смены сэмплера"""
|
||||
@@ -103,6 +111,25 @@ class SamplerManager(QWidget):
|
||||
self.api_url = new_url
|
||||
self.load_samplers()
|
||||
|
||||
def get_samplers(self):
|
||||
"""Возвращает список доступных сэмплеров"""
|
||||
return self.samplers
|
||||
|
||||
def get_sampler_names(self):
|
||||
"""Возвращает список имен сэмплеров"""
|
||||
return [sampler.name for sampler in self.samplers]
|
||||
|
||||
def get_current_sampler(self):
|
||||
"""Получить текущий выбранный сэмплер"""
|
||||
return self.sampler_combo.currentText()
|
||||
"""Возвращает текущий сэмплер"""
|
||||
return self.current_sampler
|
||||
|
||||
def set_current_sampler(self, sampler_name: str) -> bool:
|
||||
"""Установка текущего сэмплера"""
|
||||
sampler = next(
|
||||
(s for s in self.samplers if s.name == sampler_name),
|
||||
None
|
||||
)
|
||||
if sampler:
|
||||
self.current_sampler = sampler
|
||||
return True
|
||||
return False
|
||||
@@ -1,6 +1,14 @@
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from .size_presets import SIZE_PRESETS
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict
|
||||
|
||||
@dataclass
|
||||
class SizePreset:
|
||||
name: str
|
||||
width: int
|
||||
height: int
|
||||
|
||||
class SizePresetManager(QWidget):
|
||||
size_changed = pyqtSignal(int, int) # Сигнал для изменения размера
|
||||
@@ -8,6 +16,17 @@ class SizePresetManager(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setup_ui()
|
||||
self.presets = [
|
||||
SizePreset("512x512", 512, 512),
|
||||
SizePreset("768x768", 768, 768),
|
||||
SizePreset("1024x1024", 1024, 1024),
|
||||
SizePreset("512x768", 512, 768),
|
||||
SizePreset("768x512", 768, 512),
|
||||
SizePreset("512x1024", 512, 1024),
|
||||
SizePreset("1024x512", 1024, 512),
|
||||
SizePreset("768x1024", 768, 1024),
|
||||
SizePreset("1024x768", 1024, 768),
|
||||
]
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
@@ -90,4 +109,15 @@ class SizePresetManager(QWidget):
|
||||
|
||||
def on_preset_clicked(self, width, height):
|
||||
"""Обработчик нажатия на кнопку пресета"""
|
||||
self.size_changed.emit(width, height)
|
||||
self.size_changed.emit(width, height)
|
||||
|
||||
def get_preset_names(self) -> List[str]:
|
||||
"""Возвращает список имен пресетов"""
|
||||
return [preset.name for preset in self.presets]
|
||||
|
||||
def get_preset(self, name: str) -> SizePreset:
|
||||
"""Возвращает пресет по имени"""
|
||||
return next(
|
||||
(preset for preset in self.presets if preset.name == name),
|
||||
None
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+40
-35
@@ -5,20 +5,26 @@ import requests
|
||||
import os
|
||||
|
||||
class IconType(Enum):
|
||||
# Навигация
|
||||
"""Типы иконок"""
|
||||
HOME = auto()
|
||||
MODELS = auto()
|
||||
SETTINGS = auto()
|
||||
GENERATE = auto()
|
||||
ADD = auto()
|
||||
DELETE = auto()
|
||||
REFRESH = auto()
|
||||
SAVE = auto()
|
||||
CLOSE = auto()
|
||||
SUCCESS = auto()
|
||||
|
||||
# Навигация
|
||||
MENU = "menu"
|
||||
BACK = "arrow-back"
|
||||
FORWARD = "arrow-forward"
|
||||
|
||||
# Действия
|
||||
ADD = "add"
|
||||
REMOVE = "remove"
|
||||
EDIT = "edit"
|
||||
DELETE = "delete"
|
||||
SAVE = "save"
|
||||
REFRESH = "refresh"
|
||||
SEARCH = "search"
|
||||
|
||||
# Медиа
|
||||
@@ -30,13 +36,11 @@ class IconType(Enum):
|
||||
STOP = "stop"
|
||||
|
||||
# Статусы
|
||||
SUCCESS = "check-circle"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
INFO = "info"
|
||||
|
||||
# Интерфейс
|
||||
CLOSE = "close"
|
||||
MINIMIZE = "minimize"
|
||||
MAXIMIZE = "maximize"
|
||||
RESTORE = "restore"
|
||||
@@ -46,48 +50,49 @@ class IconType(Enum):
|
||||
DARK_MODE = "dark-mode"
|
||||
|
||||
class IconManager:
|
||||
"""Менеджер иконок"""
|
||||
_instance = None
|
||||
_icons = {}
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(IconManager, cls).__new__(cls)
|
||||
cls._instance._download_icons()
|
||||
return cls._instance
|
||||
def __init__(self):
|
||||
self.icons = {}
|
||||
self.load_icons()
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
"""Получить экземпляр менеджера иконок (Singleton)"""
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
cls._instance = IconManager()
|
||||
return cls._instance
|
||||
|
||||
def _download_icons(self):
|
||||
"""Загрузка иконок из интернета"""
|
||||
def load_icons(self):
|
||||
"""Загрузка иконок"""
|
||||
icons_dir = Path("resources/icons")
|
||||
icons_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
icons = {
|
||||
IconType.HOME: "https://api.iconify.design/mdi/home.svg",
|
||||
IconType.IMAGE: "https://api.iconify.design/mdi/image.svg",
|
||||
IconType.SETTINGS: "https://api.iconify.design/mdi/cog.svg"
|
||||
# Сопоставление типов иконок с файлами
|
||||
icon_files = {
|
||||
IconType.HOME: "home.svg",
|
||||
IconType.MODELS: "models.svg",
|
||||
IconType.SETTINGS: "settings.svg",
|
||||
IconType.GENERATE: "generate.svg",
|
||||
IconType.ADD: "add.svg",
|
||||
IconType.DELETE: "delete.svg",
|
||||
IconType.REFRESH: "refresh.svg",
|
||||
IconType.SAVE: "save.svg",
|
||||
IconType.CLOSE: "close.svg",
|
||||
IconType.SUCCESS: "success.svg",
|
||||
}
|
||||
|
||||
for icon_type, url in icons.items():
|
||||
icon_path = icons_dir / f"{icon_type.name.lower()}.svg"
|
||||
if not icon_path.exists():
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
with open(icon_path, "wb") as f:
|
||||
f.write(response.content)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке иконки {icon_type}: {e}")
|
||||
|
||||
self._icons[icon_type] = QIcon(str(icon_path))
|
||||
# Загружаем иконки
|
||||
for icon_type, filename in icon_files.items():
|
||||
icon_path = icons_dir / filename
|
||||
if icon_path.exists():
|
||||
self.icons[icon_type] = QIcon(str(icon_path))
|
||||
else:
|
||||
print(f"Иконка не найдена: {icon_path}")
|
||||
|
||||
def get_icon(self, icon_type: IconType) -> QIcon:
|
||||
"""Получение иконки по типу"""
|
||||
return self._icons.get(icon_type, QIcon())
|
||||
"""Получить иконку по типу"""
|
||||
return self.icons.get(icon_type, QIcon())
|
||||
|
||||
def get_themed_icon(self, icon_type: IconType, theme: str) -> QIcon:
|
||||
"""Получение иконки с учетом темы"""
|
||||
|
||||
+45
-18
@@ -2,15 +2,17 @@ from PyQt6.QtWidgets import QWidget, QStackedWidget, QVBoxLayout
|
||||
from PyQt6.QtCore import QPropertyAnimation, QParallelAnimationGroup, QEasingCurve, Qt, QPoint
|
||||
from .theme import Theme, Colors
|
||||
from .theme_manager import ThemeManager
|
||||
from .style_constants import StyleConstants
|
||||
|
||||
class BasePage(QWidget):
|
||||
"""Базовый класс для страниц"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.theme_manager = ThemeManager.get_instance()
|
||||
self.setup_ui()
|
||||
self.setup_base_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Базовая настройка страницы"""
|
||||
def setup_base_ui(self):
|
||||
"""Настройка базового UI"""
|
||||
colors = self.theme_manager.current_theme.get_colors()
|
||||
self.setStyleSheet(f"""
|
||||
QWidget {{
|
||||
@@ -18,11 +20,19 @@ class BasePage(QWidget):
|
||||
}}
|
||||
""")
|
||||
|
||||
# Основной layout
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(24, 24, 24, 24)
|
||||
self.layout.setSpacing(16)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
|
||||
# Создаем анимации
|
||||
self.enter_animation = QPropertyAnimation(self, b"pos")
|
||||
self.enter_animation.setDuration(250)
|
||||
self.enter_animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
||||
|
||||
self.exit_animation = QPropertyAnimation(self, b"pos")
|
||||
self.exit_animation.setDuration(250)
|
||||
self.exit_animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
||||
|
||||
def enter_animation(self):
|
||||
"""Анимация появления страницы"""
|
||||
# Создаем группу анимаций
|
||||
@@ -78,26 +88,43 @@ class BasePage(QWidget):
|
||||
self.anim_group.start()
|
||||
|
||||
class PageContainer(QStackedWidget):
|
||||
"""Контейнер для страниц с анимацией переходов"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.pages = {}
|
||||
self.current_page = None
|
||||
|
||||
def add_page(self, name: str, page: BasePage):
|
||||
"""Добавление новой страницы"""
|
||||
"""Добавить страницу"""
|
||||
self.pages[name] = page
|
||||
self.addWidget(page)
|
||||
|
||||
def show_page(self, name: str):
|
||||
"""Показ страницы по имени"""
|
||||
if name in self.pages:
|
||||
# Запускаем анимацию исчезновения текущей страницы
|
||||
current_page = self.currentWidget()
|
||||
if isinstance(current_page, BasePage):
|
||||
current_page.leave_animation()
|
||||
"""Показать страницу с анимацией"""
|
||||
if name not in self.pages:
|
||||
return
|
||||
|
||||
# Показываем новую страницу
|
||||
new_page = self.pages[name]
|
||||
self.setCurrentWidget(new_page)
|
||||
next_page = self.pages[name]
|
||||
|
||||
if self.current_page == next_page:
|
||||
return
|
||||
|
||||
# Запускаем анимацию появления новой страницы
|
||||
new_page.enter_animation()
|
||||
# Получаем размеры виджета
|
||||
width = self.width()
|
||||
|
||||
# Настраиваем анимации
|
||||
if self.current_page:
|
||||
# Анимация выхода текущей страницы
|
||||
self.current_page.exit_animation.setStartValue(QPoint(0, 0))
|
||||
self.current_page.exit_animation.setEndValue(QPoint(-width, 0))
|
||||
self.current_page.exit_animation.start()
|
||||
|
||||
# Анимация входа следующей страницы
|
||||
next_page.move(width, 0)
|
||||
next_page.enter_animation.setStartValue(QPoint(width, 0))
|
||||
next_page.enter_animation.setEndValue(QPoint(0, 0))
|
||||
next_page.enter_animation.start()
|
||||
|
||||
# Показываем страницу
|
||||
self.setCurrentWidget(next_page)
|
||||
self.current_page = next_page
|
||||
@@ -0,0 +1,181 @@
|
||||
from typing import Dict
|
||||
|
||||
class StyleConstants:
|
||||
# Цветовая схема
|
||||
COLORS = {
|
||||
'primary': '#7E57C2', # Основной цвет
|
||||
'secondary': '#B39DDB', # Вторичный цвет
|
||||
'accent': '#536DFE', # Акцентный цвет
|
||||
'success': '#66BB6A', # Цвет успеха
|
||||
'warning': '#FFA726', # Цвет предупреждения
|
||||
'error': '#EF5350', # Цвет ошибки
|
||||
|
||||
# Светлая тема
|
||||
'light': {
|
||||
'background': 'qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #FFFFFF, stop:1 #F5F5F5)',
|
||||
'surface': '#FFFFFF',
|
||||
'text': '#212121',
|
||||
'text_secondary': '#757575',
|
||||
'border': '#E0E0E0',
|
||||
'hover': '#F5F5F5',
|
||||
'pressed': '#EEEEEE'
|
||||
},
|
||||
|
||||
# Темная тема
|
||||
'dark': {
|
||||
'background': 'qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #1A1A1A, stop:1 #2D2D2D)',
|
||||
'surface': '#2D2D2D',
|
||||
'text': '#FFFFFF',
|
||||
'text_secondary': '#B0B0B0',
|
||||
'border': '#404040',
|
||||
'hover': '#353535',
|
||||
'pressed': '#404040'
|
||||
}
|
||||
}
|
||||
|
||||
# Размеры и отступы (в пикселях)
|
||||
SPACING = {
|
||||
'xxs': 4,
|
||||
'xs': 8,
|
||||
'sm': 12,
|
||||
'md': 16,
|
||||
'lg': 24,
|
||||
'xl': 32,
|
||||
'xxl': 48
|
||||
}
|
||||
|
||||
# Радиусы скругления (в пикселях)
|
||||
RADIUS = {
|
||||
'sm': 4,
|
||||
'md': 8,
|
||||
'lg': 12,
|
||||
'xl': 16,
|
||||
'pill': 9999
|
||||
}
|
||||
|
||||
# Тени
|
||||
SHADOWS = {
|
||||
'sm': '0 2px 4px rgba(0,0,0,0.1)',
|
||||
'md': '0 4px 8px rgba(0,0,0,0.1)',
|
||||
'lg': '0 8px 16px rgba(0,0,0,0.1)'
|
||||
}
|
||||
|
||||
# Анимации
|
||||
ANIMATIONS = {
|
||||
'duration': {
|
||||
'fast': 150,
|
||||
'normal': 250,
|
||||
'slow': 350
|
||||
},
|
||||
'easing': 'cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
}
|
||||
|
||||
# Шрифты
|
||||
FONTS = {
|
||||
'family': {
|
||||
'primary': 'Inter',
|
||||
'secondary': 'Roboto'
|
||||
},
|
||||
'size': {
|
||||
'xs': '12px',
|
||||
'sm': '14px',
|
||||
'md': '16px',
|
||||
'lg': '18px',
|
||||
'xl': '24px',
|
||||
'xxl': '32px'
|
||||
},
|
||||
'weight': {
|
||||
'regular': '400',
|
||||
'medium': '500',
|
||||
'semibold': '600',
|
||||
'bold': '700'
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_base_styles(cls, theme: str = 'light') -> str:
|
||||
"""Возвращает базовые стили для приложения"""
|
||||
colors = cls.COLORS[theme]
|
||||
|
||||
return f"""
|
||||
/* Основные стили */
|
||||
QMainWindow {{
|
||||
background: {colors['background']};
|
||||
}}
|
||||
|
||||
QWidget {{
|
||||
color: {colors['text']};
|
||||
font-family: {cls.FONTS['family']['primary']};
|
||||
font-size: {cls.FONTS['size']['md']};
|
||||
}}
|
||||
|
||||
/* Кнопки */
|
||||
QPushButton {{
|
||||
background-color: {cls.COLORS['primary']};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: {cls.RADIUS['md']}px;
|
||||
padding: {cls.SPACING['sm']}px {cls.SPACING['md']}px;
|
||||
font-weight: {cls.FONTS['weight']['medium']};
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {cls.COLORS['accent']};
|
||||
}}
|
||||
|
||||
/* Поля ввода */
|
||||
QLineEdit, QTextEdit {{
|
||||
background-color: {colors['surface']};
|
||||
color: {colors['text']};
|
||||
border: 1px solid {colors['border']};
|
||||
border-radius: {cls.RADIUS['md']}px;
|
||||
padding: {cls.SPACING['sm']}px;
|
||||
selection-background-color: {cls.COLORS['secondary']};
|
||||
}}
|
||||
|
||||
QLineEdit:focus, QTextEdit:focus {{
|
||||
border-color: {cls.COLORS['primary']};
|
||||
}}
|
||||
|
||||
/* Выпадающие списки */
|
||||
QComboBox {{
|
||||
background-color: {colors['surface']};
|
||||
color: {colors['text']};
|
||||
border: 1px solid {colors['border']};
|
||||
border-radius: {cls.RADIUS['md']}px;
|
||||
padding: {cls.SPACING['sm']}px;
|
||||
}}
|
||||
|
||||
QComboBox:hover {{
|
||||
border-color: {cls.COLORS['primary']};
|
||||
}}
|
||||
|
||||
/* Полосы прокрутки */
|
||||
QScrollBar:vertical {{
|
||||
background-color: {colors['surface']};
|
||||
width: 12px;
|
||||
margin: 0;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical {{
|
||||
background-color: {cls.COLORS['secondary']};
|
||||
min-height: 20px;
|
||||
border-radius: {cls.RADIUS['sm']}px;
|
||||
}}
|
||||
|
||||
QScrollBar:horizontal {{
|
||||
background-color: {colors['surface']};
|
||||
height: 12px;
|
||||
margin: 0;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal {{
|
||||
background-color: {cls.COLORS['secondary']};
|
||||
min-width: 20px;
|
||||
border-radius: {cls.RADIUS['sm']}px;
|
||||
}}
|
||||
"""
|
||||
@@ -0,0 +1,91 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QTextEdit, QComboBox)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QTextCursor
|
||||
from ..styles.style_constants import StyleConstants
|
||||
from ..styles.icons import IconManager, IconType
|
||||
|
||||
class ConsoleWindow(QWidget):
|
||||
"""Окно для консольного вывода"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.icon_manager = IconManager.get_instance()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setWindowTitle("Консоль")
|
||||
self.resize(600, 400)
|
||||
|
||||
# Основной layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(StyleConstants.SPACING['md'])
|
||||
layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
|
||||
# Верхняя панель
|
||||
top_panel = QHBoxLayout()
|
||||
|
||||
# Уровень логирования
|
||||
self.log_level = QComboBox()
|
||||
self.log_level.addItems(["INFO", "WARNING", "ERROR", "DEBUG"])
|
||||
top_panel.addWidget(self.log_level)
|
||||
|
||||
# Кнопки
|
||||
clear_btn = QPushButton("Очистить")
|
||||
clear_btn.setIcon(self.icon_manager.get_icon(IconType.DELETE))
|
||||
clear_btn.clicked.connect(self.clear_console)
|
||||
top_panel.addWidget(clear_btn)
|
||||
|
||||
save_btn = QPushButton("Сохранить")
|
||||
save_btn.setIcon(self.icon_manager.get_icon(IconType.SAVE))
|
||||
top_panel.addWidget(save_btn)
|
||||
|
||||
top_panel.addStretch()
|
||||
layout.addLayout(top_panel)
|
||||
|
||||
# Консоль
|
||||
self.console = QTextEdit()
|
||||
self.console.setReadOnly(True)
|
||||
self.console.setStyleSheet(f"""
|
||||
QTextEdit {{
|
||||
background-color: {StyleConstants.COLORS['dark']['background']};
|
||||
color: {StyleConstants.COLORS['dark']['text']};
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: {StyleConstants.FONTS['size']['md']};
|
||||
padding: {StyleConstants.SPACING['md']}px;
|
||||
border-radius: {StyleConstants.RADIUS['md']}px;
|
||||
}}
|
||||
""")
|
||||
layout.addWidget(self.console)
|
||||
|
||||
# Применяем стили
|
||||
self.setStyleSheet(StyleConstants.get_base_styles('light'))
|
||||
|
||||
def log(self, message: str, level: str = "INFO"):
|
||||
"""Добавление сообщения в консоль"""
|
||||
color = {
|
||||
"INFO": StyleConstants.COLORS['primary'],
|
||||
"WARNING": StyleConstants.COLORS['warning'],
|
||||
"ERROR": StyleConstants.COLORS['error'],
|
||||
"DEBUG": StyleConstants.COLORS['secondary']
|
||||
}.get(level, StyleConstants.COLORS['primary'])
|
||||
|
||||
# Форматируем сообщение
|
||||
formatted_message = f'<span style="color: {color}">[{level}]</span> {message}<br>'
|
||||
|
||||
# Добавляем сообщение в консоль
|
||||
cursor = self.console.textCursor()
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.insertHtml(formatted_message)
|
||||
|
||||
# Прокручиваем к последнему сообщению
|
||||
self.console.setTextCursor(cursor)
|
||||
self.console.ensureCursorVisible()
|
||||
|
||||
def clear_console(self):
|
||||
"""Очистка консоли"""
|
||||
self.console.clear()
|
||||
@@ -0,0 +1,153 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QTextEdit, QComboBox,
|
||||
QScrollArea, QFrame, QCheckBox)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from ..styles.style_constants import StyleConstants
|
||||
from ..styles.icons import IconManager, IconType
|
||||
|
||||
class NegativePromptCategory(QFrame):
|
||||
"""Виджет категории негативных промптов"""
|
||||
def __init__(self, title: str, prompts: dict, parent=None):
|
||||
super().__init__(parent)
|
||||
self.title = title
|
||||
self.prompts = prompts
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Заголовок категории
|
||||
title_label = QLabel(self.title)
|
||||
title_label.setStyleSheet(f"""
|
||||
font-size: {StyleConstants.FONTS['size']['lg']};
|
||||
font-weight: {StyleConstants.FONTS['weight']['bold']};
|
||||
margin-bottom: {StyleConstants.SPACING['sm']};
|
||||
""")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Список промптов с чекбоксами
|
||||
for prompt, description in self.prompts.items():
|
||||
prompt_frame = QFrame()
|
||||
prompt_layout = QVBoxLayout(prompt_frame)
|
||||
|
||||
checkbox = QCheckBox(prompt)
|
||||
checkbox.setStyleSheet(f"""
|
||||
font-size: {StyleConstants.FONTS['size']['md']};
|
||||
font-weight: {StyleConstants.FONTS['weight']['medium']};
|
||||
""")
|
||||
|
||||
description_label = QLabel(description)
|
||||
description_label.setStyleSheet(f"""
|
||||
color: {StyleConstants.COLORS['light']['text_secondary']};
|
||||
font-size: {StyleConstants.FONTS['size']['sm']};
|
||||
""")
|
||||
description_label.setWordWrap(True)
|
||||
|
||||
prompt_layout.addWidget(checkbox)
|
||||
prompt_layout.addWidget(description_label)
|
||||
layout.addWidget(prompt_frame)
|
||||
|
||||
self.setStyleSheet(f"""
|
||||
QFrame {{
|
||||
background-color: {StyleConstants.COLORS['light']['surface']};
|
||||
border-radius: {StyleConstants.RADIUS['lg']};
|
||||
padding: {StyleConstants.SPACING['md']};
|
||||
margin-bottom: {StyleConstants.SPACING['md']};
|
||||
}}
|
||||
""")
|
||||
|
||||
class NegativePromptWindow(QWidget):
|
||||
"""Окно для работы с негативными промптами"""
|
||||
prompts_selected = pyqtSignal(list)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.icon_manager = IconManager.get_instance()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setWindowTitle("Негативные промпты")
|
||||
self.resize(800, 600)
|
||||
|
||||
# Основной layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(StyleConstants.SPACING['md'])
|
||||
layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
|
||||
# Верхняя панель
|
||||
top_panel = QHBoxLayout()
|
||||
|
||||
# Поиск
|
||||
self.search_box = QComboBox()
|
||||
self.search_box.setEditable(True)
|
||||
self.search_box.setPlaceholderText("Поиск негативных промптов...")
|
||||
self.search_box.setMinimumWidth(300)
|
||||
top_panel.addWidget(self.search_box)
|
||||
|
||||
# Кнопки
|
||||
select_all_btn = QPushButton("Выбрать все")
|
||||
select_all_btn.setIcon(self.icon_manager.get_icon(IconType.SUCCESS))
|
||||
top_panel.addWidget(select_all_btn)
|
||||
|
||||
clear_btn = QPushButton("Очистить")
|
||||
clear_btn.setIcon(self.icon_manager.get_icon(IconType.CLOSE))
|
||||
top_panel.addWidget(clear_btn)
|
||||
|
||||
top_panel.addStretch()
|
||||
layout.addLayout(top_panel)
|
||||
|
||||
# Область прокрутки для категорий
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
||||
# Контейнер для категорий
|
||||
categories_widget = QWidget()
|
||||
self.categories_layout = QVBoxLayout(categories_widget)
|
||||
|
||||
# Добавляем тестовые категории
|
||||
test_categories = {
|
||||
"Качество": {
|
||||
"Низкое качество": "Удаляет артефакты низкого качества и размытость",
|
||||
"Шум и зернистость": "Убирает нежелательный шум и зернистость",
|
||||
"Искажения": "Предотвращает искажения пропорций и перспективы"
|
||||
},
|
||||
"Артефакты": {
|
||||
"Двойные элементы": "Убирает дублирование частей изображения",
|
||||
"Размытые лица": "Улучшает четкость и детализацию лиц",
|
||||
"Неправильная анатомия": "Корректирует анатомические ошибки"
|
||||
}
|
||||
}
|
||||
|
||||
for title, prompts in test_categories.items():
|
||||
category = NegativePromptCategory(title, prompts)
|
||||
self.categories_layout.addWidget(category)
|
||||
|
||||
self.categories_layout.addStretch()
|
||||
scroll_area.setWidget(categories_widget)
|
||||
layout.addWidget(scroll_area)
|
||||
|
||||
# Нижняя панель
|
||||
bottom_panel = QHBoxLayout()
|
||||
|
||||
apply_btn = QPushButton("Применить")
|
||||
apply_btn.setIcon(self.icon_manager.get_icon(IconType.SUCCESS))
|
||||
apply_btn.setStyleSheet(f"""
|
||||
background-color: {StyleConstants.COLORS['success']};
|
||||
""")
|
||||
bottom_panel.addWidget(apply_btn)
|
||||
|
||||
cancel_btn = QPushButton("Отмена")
|
||||
cancel_btn.setIcon(self.icon_manager.get_icon(IconType.CLOSE))
|
||||
bottom_panel.addWidget(cancel_btn)
|
||||
|
||||
bottom_panel.addStretch()
|
||||
layout.addLayout(bottom_panel)
|
||||
|
||||
# Применяем стили
|
||||
self.setStyleSheet(StyleConstants.get_base_styles('light'))
|
||||
@@ -0,0 +1,117 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QTextEdit, QComboBox,
|
||||
QScrollArea, QFrame)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from ..styles.style_constants import StyleConstants
|
||||
from ..styles.icons import IconManager, IconType
|
||||
|
||||
class PromptCategory(QFrame):
|
||||
"""Виджет категории промптов"""
|
||||
def __init__(self, title: str, prompts: list, parent=None):
|
||||
super().__init__(parent)
|
||||
self.title = title
|
||||
self.prompts = prompts
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Заголовок категории
|
||||
title_label = QLabel(self.title)
|
||||
title_label.setStyleSheet(f"""
|
||||
font-size: {StyleConstants.FONTS['size']['lg']};
|
||||
font-weight: {StyleConstants.FONTS['weight']['bold']};
|
||||
margin-bottom: {StyleConstants.SPACING['sm']};
|
||||
""")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Список промптов
|
||||
for prompt in self.prompts:
|
||||
btn = QPushButton(prompt)
|
||||
btn.setStyleSheet(f"""
|
||||
text-align: left;
|
||||
padding: {StyleConstants.SPACING['sm']};
|
||||
margin-bottom: {StyleConstants.SPACING['xs']};
|
||||
""")
|
||||
layout.addWidget(btn)
|
||||
|
||||
self.setStyleSheet(f"""
|
||||
QFrame {{
|
||||
background-color: {StyleConstants.COLORS['light']['surface']};
|
||||
border-radius: {StyleConstants.RADIUS['lg']};
|
||||
padding: {StyleConstants.SPACING['md']};
|
||||
margin-bottom: {StyleConstants.SPACING['md']};
|
||||
}}
|
||||
""")
|
||||
|
||||
class PromptWindow(QWidget):
|
||||
"""Окно для работы с промптами"""
|
||||
prompt_selected = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.icon_manager = IconManager.get_instance()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setWindowTitle("Библиотека промптов")
|
||||
self.resize(800, 600)
|
||||
|
||||
# Основной layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(StyleConstants.SPACING['md'])
|
||||
layout.setContentsMargins(
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg'],
|
||||
StyleConstants.SPACING['lg']
|
||||
)
|
||||
|
||||
# Верхняя панель
|
||||
top_panel = QHBoxLayout()
|
||||
|
||||
# Поиск
|
||||
self.search_box = QComboBox()
|
||||
self.search_box.setEditable(True)
|
||||
self.search_box.setPlaceholderText("Поиск промптов...")
|
||||
self.search_box.setMinimumWidth(300)
|
||||
top_panel.addWidget(self.search_box)
|
||||
|
||||
# Кнопки
|
||||
add_btn = QPushButton("Добавить промпт")
|
||||
add_btn.setIcon(self.icon_manager.get_icon(IconType.ADD))
|
||||
top_panel.addWidget(add_btn)
|
||||
|
||||
refresh_btn = QPushButton("Обновить")
|
||||
refresh_btn.setIcon(self.icon_manager.get_icon(IconType.REFRESH))
|
||||
top_panel.addWidget(refresh_btn)
|
||||
|
||||
top_panel.addStretch()
|
||||
layout.addLayout(top_panel)
|
||||
|
||||
# Область прокрутки для категорий
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
||||
# Контейнер для категорий
|
||||
categories_widget = QWidget()
|
||||
self.categories_layout = QVBoxLayout(categories_widget)
|
||||
|
||||
# Добавляем тестовые категории
|
||||
test_categories = {
|
||||
"Стили": ["Фотореалистичный", "Аниме", "Масляная живопись", "Акварель"],
|
||||
"Освещение": ["Дневной свет", "Закат", "Ночное освещение", "Студийный свет"],
|
||||
"Композиция": ["Крупный план", "Панорама", "Портрет", "Пейзаж"]
|
||||
}
|
||||
|
||||
for title, prompts in test_categories.items():
|
||||
category = PromptCategory(title, prompts)
|
||||
self.categories_layout.addWidget(category)
|
||||
|
||||
self.categories_layout.addStretch()
|
||||
scroll_area.setWidget(categories_widget)
|
||||
layout.addWidget(scroll_area)
|
||||
|
||||
# Применяем стили
|
||||
self.setStyleSheet(StyleConstants.get_base_styles('light'))
|
||||
Reference in new issue
Block a user