from PySide6.QtWidgets import (QMainWindow, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QWidget, QTextEdit, QLineEdit) from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QPixmap from core.ui.mainwindow import Ui_MainWindow from core.agent.agent import Agent from core.character.character import Character from core.llm.ollamaapi import OllamaProvider class CharacterListDialog(QDialog): """Диалоговое окно со списком персонажей""" def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Выбор персонажа") self.setMinimumSize(500, 400) self.selected_character = None layout = QVBoxLayout(self) # Заголовок title = QLabel("Выберите персонажа:") title.setStyleSheet("font-size: 16px; font-weight: bold;") layout.addWidget(title) # Список персонажей self.list_widget = QListWidget() self.list_widget.itemDoubleClicked.connect(self.select_character) layout.addWidget(self.list_widget) # Кнопки btn_layout = QHBoxLayout() select_btn = QPushButton("Выбрать") select_btn.clicked.connect(self.select_character) btn_layout.addWidget(select_btn) close_btn = QPushButton("Отмена") close_btn.clicked.connect(self.close) btn_layout.addWidget(close_btn) layout.addLayout(btn_layout) self.load_characters() def load_characters(self): """Загружает список персонажей""" characters = Agent.get_all_char_info() for char in characters: item = QListWidgetItem() item.setData(Qt.UserRole, char) # Сохраняем данные персонажа # Создаём кастомный виджет для строки списка widget = QWidget() widget.setFixedHeight(60) widget_layout = QHBoxLayout(widget) widget_layout.setContentsMargins(5, 5, 5, 5) # Аватар avatar_label = QLabel() avatar_label.setFixedSize(60, 60) avatar_path = char.get('avatar_path') if avatar_path: pixmap = QPixmap(avatar_path) if not pixmap.isNull(): avatar_label.setPixmap(pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation)) else: avatar_label.setText("❌") avatar_label.setAlignment(Qt.AlignCenter) else: avatar_label.setText("Нет") avatar_label.setAlignment(Qt.AlignCenter) widget_layout.addWidget(avatar_label) # Имя и ID name = char.get('name', 'Без имени') char_id = char.get('id', 'N/A') info_label = QLabel(f"{name}
ID: {char_id}") info_label.setTextFormat(Qt.RichText) widget_layout.addWidget(info_label) widget_layout.addStretch() self.list_widget.addItem(item) self.list_widget.setItemWidget(item, widget) def select_character(self): """Выбирает персонажа и закрывает диалог""" current_item = self.list_widget.currentItem() if current_item: self.selected_character = current_item.data(Qt.UserRole) self.accept() # Закрывает диалог с кодом Accepted class GenerationThread(QThread): """Поток для генерации ответа (чтобы не блокировать UI)""" response_ready = Signal(str) error_occurred = Signal(str) def __init__(self, agent, user_input): super().__init__() self.agent = agent self.user_input = user_input def run(self): try: response = self.agent.respond(self.user_input) self.response_ready.emit(response) except Exception as e: self.error_occurred.emit(str(e)) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) # Состояние приложения self.current_agent = None self.current_character = None self.generation_thread = None # Настройка UI self.setup_chat_ui() # Подключение кнопок self.ui.pushButton.clicked.connect(self.show_character_list) self.ui.pushButton_2.clicked.connect(self.show_profile) self.ui.pushButton_3.clicked.connect(self.show_settings) def setup_chat_ui(self): """Настраивает UI чата""" # Находим centralwidget и добавляем чат self.chat_widget = self.ui.centralwidget # Создаём вертикальный layout для centralwidget chat_layout = QVBoxLayout(self.chat_widget) chat_layout.setContentsMargins(10, 70, 10, 10) # Отступы сверху для кнопок # Область чата (только чтение) self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) self.chat_display.setPlaceholderText("Выберите персонажа для начала чата...") chat_layout.addWidget(self.chat_display) # Панель ввода input_layout = QHBoxLayout() self.message_input = QLineEdit() self.message_input.setPlaceholderText("Введите сообщение...") self.message_input.returnPressed.connect(self.send_message) input_layout.addWidget(self.message_input) self.send_button = QPushButton("Отправить") self.send_button.clicked.connect(self.send_message) self.send_button.setEnabled(False) # До выбора персонажа недоступна input_layout.addWidget(self.send_button) chat_layout.addLayout(input_layout) def show_character_list(self): """Показывает диалог выбора персонажа""" dialog = CharacterListDialog(self) if dialog.exec() == QDialog.Accepted and dialog.selected_character: self.select_character(dialog.selected_character) def select_character(self, char_data): """Выбирает персонажа и загружает его данные""" char_id = char_data.get('id') try: # Загружаем персонажа self.current_character = Character.load(char_id) # Создаём агента (используем заглушку для LLM) # В реальном приложении нужно выбрать модель self.current_agent = Agent( self.current_character, OllamaProvider("qwen3-vl:8b"), # Модель по умолчанию user_name="Gloomer" ) # Загружаем память (историю чата) self.current_agent.load_memory() self.current_agent.ensure_first_message() # Обновляем UI self.update_chat_display() self.send_button.setEnabled(True) self.message_input.setEnabled(True) self.message_input.setFocus() # Показываем информацию о выбранном персонаже self.chat_display.append(f"Выбран персонаж: {self.current_character.display_name}\n") except Exception as e: self.chat_display.append(f"Ошибка загрузки персонажа: {str(e)}\n") def update_chat_display(self): """Обновляет отображение истории чата""" if not self.current_agent: return self.chat_display.clear() for msg in self.current_agent.chat_history: role = msg.get('role', '') content = msg.get('content', '') if role == 'user': self.chat_display.append(f"Вы: {content}\n") elif role == 'assistant': self.chat_display.append(f"{self.current_character.name}: {content}\n") def send_message(self): """Отправляет сообщение и получает ответ""" if not self.current_agent or not self.message_input.text().strip(): return user_input = self.message_input.text().strip() self.message_input.clear() # Показываем сообщение пользователя self.chat_display.append(f"Вы: {user_input}\n") self.chat_display.append("Думаю...\n") # Блокируем ввод на время генерации self.message_input.setEnabled(False) self.send_button.setEnabled(False) # Запускаем генерацию в отдельном потоке self.generation_thread = GenerationThread(self.current_agent, user_input) self.generation_thread.response_ready.connect(self.on_response_ready) self.generation_thread.error_occurred.connect(self.on_error) self.generation_thread.start() def on_response_ready(self, response): """Обрабатывает готовый ответ от LLM""" # Удаляем "Думаю..." cursor = self.chat_display.textCursor() cursor.movePosition(cursor.Start) cursor.select(cursor.LineUnderCursor) cursor.removeSelectedText() cursor.deleteChar() # Удаляем \n # Показываем ответ self.chat_display.append(f"{self.current_character.name}: {response}\n") # Сохраняем память self.current_agent.save_memory() # Разблокируем ввод self.message_input.setEnabled(True) self.send_button.setEnabled(True) self.message_input.setFocus() def on_error(self, error_msg): """Обрабатывает ошибку генерации""" self.chat_display.append(f"Ошибка: {error_msg}\n") # Разблокируем ввод self.message_input.setEnabled(True) self.send_button.setEnabled(True) self.message_input.setFocus() def show_profile(self): """Показывает профиль персонажа (заглушка)""" if self.current_character: self.chat_display.append(f"Профиль: {self.current_character.display_name}\n") else: self.chat_display.append("Сначала выберите персонажа\n") def show_settings(self): """Показывает настройки инференса (заглушка)""" if self.current_character: self.chat_display.append( f"Настройки:\n" f"- Temperature: {self.current_character.temperature}\n" f"- Max Tokens: {self.current_character.max_tokens}\n" ) else: self.chat_display.append("Сначала выберите персонажа\n")