Initial commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'mainwindow.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.11.0
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMainWindow, QPushButton,
|
||||
QSizePolicy, QWidget)
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
if not MainWindow.objectName():
|
||||
MainWindow.setObjectName(u"MainWindow")
|
||||
MainWindow.resize(640, 480)
|
||||
MainWindow.setStyleSheet(u"background-color: qlineargradient(spread:pad, x1:1, y1:1, x2:0, y2:0, stop:0 rgba(81, 0, 135, 255), stop:0.427447 rgba(41, 61, 132, 235), stop:1 rgba(155, 79, 165, 255))")
|
||||
self.centralwidget = QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
self.widget = QWidget(self.centralwidget)
|
||||
self.widget.setObjectName(u"widget")
|
||||
self.widget.setGeometry(QRect(10, 10, 621, 51))
|
||||
self.horizontalLayout = QHBoxLayout(self.widget)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.pushButton = QPushButton(self.widget)
|
||||
self.pushButton.setObjectName(u"pushButton")
|
||||
self.pushButton.setMouseTracking(False)
|
||||
|
||||
self.horizontalLayout.addWidget(self.pushButton)
|
||||
|
||||
self.pushButton_2 = QPushButton(self.widget)
|
||||
self.pushButton_2.setObjectName(u"pushButton_2")
|
||||
|
||||
self.horizontalLayout.addWidget(self.pushButton_2)
|
||||
|
||||
self.pushButton_3 = QPushButton(self.widget)
|
||||
self.pushButton_3.setObjectName(u"pushButton_3")
|
||||
|
||||
self.horizontalLayout.addWidget(self.pushButton_3)
|
||||
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
|
||||
QMetaObject.connectSlotsByName(MainWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"LAIC Local AI Character", None))
|
||||
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439", None))
|
||||
self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"\u041f\u0440\u043e\u0444\u0438\u043b\u044c", None))
|
||||
self.pushButton_3.setText(QCoreApplication.translate("MainWindow", u"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043d\u0444\u0435\u0440\u0435\u043d\u0441\u0430", None))
|
||||
# retranslateUi
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>640</width>
|
||||
<height>480</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>LAIC Local AI Character</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: qlineargradient(spread:pad, x1:1, y1:1, x2:0, y2:0, stop:0 rgba(81, 0, 135, 255), stop:0.427447 rgba(41, 61, 132, 235), stop:1 rgba(155, 79, 165, 255))</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QWidget" name="">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>621</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="mouseTracking">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Список персонажей</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="text">
|
||||
<string>Профиль</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="text">
|
||||
<string>Настройки инференса</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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