Files
LAIC/core/ui/uimain.py
2026-04-29 07:19:21 +03:00

287 lines
12 KiB
Python

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"<b>{name}</b><br><small>ID: {char_id}</small>")
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"<b>Выбран персонаж:</b> {self.current_character.display_name}\n")
except Exception as e:
self.chat_display.append(f"<b>Ошибка загрузки персонажа:</b> {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"<b>Вы:</b> {content}\n")
elif role == 'assistant':
self.chat_display.append(f"<b>{self.current_character.name}:</b> {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"<b>Вы:</b> {user_input}\n")
self.chat_display.append("<i>Думаю...</i>\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"<b>{self.current_character.name}:</b> {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"<b>Ошибка:</b> {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"<b>Профиль:</b> {self.current_character.display_name}\n")
else:
self.chat_display.append("<b>Сначала выберите персонажа</b>\n")
def show_settings(self):
"""Показывает настройки инференса (заглушка)"""
if self.current_character:
self.chat_display.append(
f"<b>Настройки:</b>\n"
f"- Temperature: {self.current_character.temperature}\n"
f"- Max Tokens: {self.current_character.max_tokens}\n"
)
else:
self.chat_display.append("<b>Сначала выберите персонажа</b>\n")