186 lines
7.5 KiB
Python
186 lines
7.5 KiB
Python
|
|
# ui/widgets/right_panel.py
|
||
|
|
import json
|
||
|
|
from typing import Optional # <-- Добавлен пропущенный импорт
|
||
|
|
from PyQt6.QtWidgets import (
|
||
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTabWidget,
|
||
|
|
QTextEdit, QLineEdit, QPushButton, QFormLayout, QComboBox, QMessageBox, QApplication
|
||
|
|
)
|
||
|
|
from PyQt6.QtGui import QPixmap
|
||
|
|
from PyQt6.QtCore import Qt, pyqtSignal
|
||
|
|
|
||
|
|
class RightPanel(QWidget):
|
||
|
|
save_clicked = pyqtSignal(int, dict)
|
||
|
|
send_to_comfy_clicked = pyqtSignal(str)
|
||
|
|
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(parent)
|
||
|
|
self.current_file_id = None
|
||
|
|
self.current_filepath = None
|
||
|
|
self.current_raw_prompt = None
|
||
|
|
self.init_ui()
|
||
|
|
|
||
|
|
def init_ui(self):
|
||
|
|
layout = QVBoxLayout(self)
|
||
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
||
|
|
|
||
|
|
self.preview_label = QLabel("Нет выделенного изображения")
|
||
|
|
self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
|
|
self.preview_label.setMinimumHeight(250)
|
||
|
|
self.preview_label.setStyleSheet(
|
||
|
|
"QLabel { background-color: #0f0f0f; border: 1px solid #2d2d2d; border-radius: 4px; }"
|
||
|
|
)
|
||
|
|
layout.addWidget(self.preview_label)
|
||
|
|
|
||
|
|
self.tabs = QTabWidget()
|
||
|
|
|
||
|
|
self.gen_tab = QWidget()
|
||
|
|
gen_layout = QVBoxLayout(self.gen_tab)
|
||
|
|
gen_layout.setContentsMargins(5, 5, 5, 5)
|
||
|
|
|
||
|
|
form_layout = QFormLayout()
|
||
|
|
self.model_input = QLineEdit()
|
||
|
|
self.model_input.setReadOnly(True)
|
||
|
|
form_layout.addRow("Модель:", self.model_input)
|
||
|
|
|
||
|
|
self.sampler_input = QLineEdit()
|
||
|
|
self.sampler_input.setReadOnly(True)
|
||
|
|
form_layout.addRow("Сэмплер:", self.sampler_input)
|
||
|
|
|
||
|
|
params_row = QHBoxLayout()
|
||
|
|
self.seed_input = QLineEdit()
|
||
|
|
self.seed_input.setReadOnly(True)
|
||
|
|
self.steps_input = QLineEdit()
|
||
|
|
self.steps_input.setReadOnly(True)
|
||
|
|
self.cfg_input = QLineEdit()
|
||
|
|
self.cfg_input.setReadOnly(True)
|
||
|
|
|
||
|
|
params_row.addWidget(QLabel("Seed:"))
|
||
|
|
params_row.addWidget(self.seed_input)
|
||
|
|
params_row.addWidget(QLabel("Steps:"))
|
||
|
|
params_row.addWidget(self.steps_input)
|
||
|
|
params_row.addWidget(QLabel("CFG:"))
|
||
|
|
params_row.addWidget(self.cfg_input)
|
||
|
|
form_layout.addRow(params_row)
|
||
|
|
|
||
|
|
self.rating_combo = QComboBox()
|
||
|
|
self.rating_combo.addItems(["Без рейтинга", "★", "★★", "★★★", "★★★★", "★★★★★"])
|
||
|
|
form_layout.addRow("Рейтинг:", self.rating_combo)
|
||
|
|
gen_layout.addLayout(form_layout)
|
||
|
|
|
||
|
|
gen_layout.addWidget(QLabel("Позитивный промт:"))
|
||
|
|
self.positive_text = QTextEdit()
|
||
|
|
gen_layout.addWidget(self.positive_text)
|
||
|
|
|
||
|
|
gen_layout.addWidget(QLabel("Негативный промт:"))
|
||
|
|
self.negative_text = QTextEdit()
|
||
|
|
gen_layout.addWidget(self.negative_text)
|
||
|
|
|
||
|
|
self.save_btn = QPushButton("Сохранить изменения")
|
||
|
|
self.save_btn.clicked.connect(self._on_save_clicked)
|
||
|
|
gen_layout.addWidget(self.save_btn)
|
||
|
|
|
||
|
|
self.workflow_tab = QWidget()
|
||
|
|
wf_layout = QVBoxLayout(self.workflow_tab)
|
||
|
|
wf_layout.setContentsMargins(5, 5, 5, 5)
|
||
|
|
self.workflow_text = QTextEdit()
|
||
|
|
self.workflow_text.setReadOnly(True)
|
||
|
|
wf_layout.addWidget(self.workflow_text)
|
||
|
|
|
||
|
|
self.tabs.addTab(self.gen_tab, "Generation")
|
||
|
|
self.tabs.addTab(self.workflow_tab, "Workflow")
|
||
|
|
layout.addWidget(self.tabs)
|
||
|
|
|
||
|
|
export_layout = QHBoxLayout()
|
||
|
|
self.copy_pos_btn = QPushButton("Коп. Pos")
|
||
|
|
self.copy_pos_btn.clicked.connect(self._copy_positive)
|
||
|
|
self.copy_neg_btn = QPushButton("Коп. Neg")
|
||
|
|
self.copy_neg_btn.clicked.connect(self._copy_negative)
|
||
|
|
self.copy_wf_btn = QPushButton("Коп. Work")
|
||
|
|
self.copy_wf_btn.clicked.connect(self._copy_workflow)
|
||
|
|
export_layout.addWidget(self.copy_pos_btn)
|
||
|
|
export_layout.addWidget(self.copy_neg_btn)
|
||
|
|
export_layout.addWidget(self.copy_wf_btn)
|
||
|
|
layout.addLayout(export_layout)
|
||
|
|
|
||
|
|
self.send_btn = QPushButton("Отправить в ComfyUI")
|
||
|
|
self.send_btn.clicked.connect(self._on_send_clicked)
|
||
|
|
layout.addWidget(self.send_btn)
|
||
|
|
|
||
|
|
def display_metadata(self, file_details: Optional[dict]):
|
||
|
|
if not file_details:
|
||
|
|
self.clear_fields()
|
||
|
|
return
|
||
|
|
self.current_file_id = file_details["id"]
|
||
|
|
self.current_filepath = file_details["filepath"]
|
||
|
|
self.current_raw_prompt = file_details.get("prompt_json")
|
||
|
|
|
||
|
|
pixmap = QPixmap(self.current_filepath)
|
||
|
|
if not pixmap.isNull():
|
||
|
|
scaled = pixmap.scaled(
|
||
|
|
self.preview_label.width(), self.preview_label.height(),
|
||
|
|
Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation
|
||
|
|
)
|
||
|
|
self.preview_label.setPixmap(scaled)
|
||
|
|
else:
|
||
|
|
self.preview_label.setText("Ошибка рендеринга превью")
|
||
|
|
|
||
|
|
self.model_input.setText(file_details.get("model_name") or "Не указано")
|
||
|
|
self.sampler_input.setText(file_details.get("sampler") or "Не указано")
|
||
|
|
self.seed_input.setText(str(file_details.get("seed") or ""))
|
||
|
|
self.steps_input.setText(str(file_details.get("steps") or ""))
|
||
|
|
self.cfg_input.setText(str(file_details.get("cfg") or ""))
|
||
|
|
|
||
|
|
rating = file_details.get("rating", 0)
|
||
|
|
self.rating_combo.setCurrentIndex(rating if 0 <= rating <= 5 else 0)
|
||
|
|
|
||
|
|
self.positive_text.setPlainText(file_details.get("positive_prompt") or "")
|
||
|
|
self.negative_text.setPlainText(file_details.get("negative_prompt") or "")
|
||
|
|
|
||
|
|
wf_raw = file_details.get("workflow_json")
|
||
|
|
if wf_raw:
|
||
|
|
try:
|
||
|
|
self.workflow_text.setPlainText(json.dumps(json.loads(wf_raw), indent=2, ensure_ascii=False))
|
||
|
|
except Exception:
|
||
|
|
self.workflow_text.setPlainText(wf_raw)
|
||
|
|
else:
|
||
|
|
self.workflow_text.setPlainText("Workflow не найден")
|
||
|
|
|
||
|
|
def clear_fields(self):
|
||
|
|
self.current_file_id = None
|
||
|
|
self.current_filepath = None
|
||
|
|
self.current_raw_prompt = None
|
||
|
|
self.preview_label.setText("Нет выделенного изображения")
|
||
|
|
self.preview_label.setPixmap(QPixmap())
|
||
|
|
self.model_input.clear()
|
||
|
|
self.sampler_input.clear()
|
||
|
|
self.seed_input.clear()
|
||
|
|
self.steps_input.clear()
|
||
|
|
self.cfg_input.clear()
|
||
|
|
self.rating_combo.setCurrentIndex(0)
|
||
|
|
self.positive_text.clear()
|
||
|
|
self.negative_text.clear()
|
||
|
|
self.workflow_text.clear()
|
||
|
|
|
||
|
|
def _on_save_clicked(self):
|
||
|
|
if self.current_file_id is None: return
|
||
|
|
payload = {
|
||
|
|
"positive_prompt": self.positive_text.toPlainText(),
|
||
|
|
"negative_prompt": self.negative_text.toPlainText(),
|
||
|
|
"rating": self.rating_combo.currentIndex()
|
||
|
|
}
|
||
|
|
self.save_clicked.emit(self.current_file_id, payload)
|
||
|
|
|
||
|
|
def _on_send_clicked(self):
|
||
|
|
if not self.current_raw_prompt:
|
||
|
|
QMessageBox.warning(self, "Ошибка", "У изображения отсутствует prompt-граф.")
|
||
|
|
return
|
||
|
|
self.send_to_comfy_clicked.emit(self.current_raw_prompt)
|
||
|
|
|
||
|
|
def _copy_positive(self):
|
||
|
|
QApplication.clipboard().setText(self.positive_text.toPlainText())
|
||
|
|
|
||
|
|
def _copy_negative(self):
|
||
|
|
QApplication.clipboard().setText(self.negative_text.toPlainText())
|
||
|
|
|
||
|
|
def _copy_workflow(self):
|
||
|
|
QApplication.clipboard().setText(self.workflow_text.toPlainText())
|