Initial commit
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user