mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-07-27 08:49:01 +08:00
183 lines
6.3 KiB
Python
183 lines
6.3 KiB
Python
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
|
||
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||
|
||
from qfluentwidgets import TextEdit, LineEdit, PushButton, TitleLabel, SubtitleLabel, FluentIcon, InfoBar, InfoBarPosition
|
||
import requests
|
||
import json
|
||
|
||
class AIWorker(QThread):
|
||
response_signal = pyqtSignal(str)
|
||
done_signal = pyqtSignal()
|
||
error_signal = pyqtSignal(str) # 新增
|
||
|
||
def __init__(self, prompt, parent=None):
|
||
super().__init__(parent)
|
||
self.prompt = prompt
|
||
|
||
def run(self):
|
||
url = "http://154.37.215.220:11434/api/generate"
|
||
payload = {
|
||
"model": "qwen3:0.6b",
|
||
"prompt": self.prompt
|
||
}
|
||
try:
|
||
response = requests.post(url, json=payload, stream=True, timeout=60)
|
||
got_response = False
|
||
for line in response.iter_lines():
|
||
if line:
|
||
got_response = True
|
||
try:
|
||
data = json.loads(line.decode('utf-8'))
|
||
self.response_signal.emit(data.get("response", ""))
|
||
if data.get("done", False):
|
||
self.done_signal.emit()
|
||
break
|
||
except Exception:
|
||
continue
|
||
if not got_response:
|
||
self.error_signal.emit("服务器繁忙,请稍后再试。")
|
||
self.done_signal.emit()
|
||
except requests.ConnectionError:
|
||
self.error_signal.emit("网络连接失败,请检查网络设置。")
|
||
self.done_signal.emit()
|
||
except Exception as e:
|
||
self.error_signal.emit(f"[错误]: {str(e)}")
|
||
self.done_signal.emit()
|
||
|
||
|
||
class AIInterface(QWidget):
|
||
MAX_HISTORY = 20 # 新增最大对话条数
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.setObjectName("aiPage")
|
||
self.layout = QVBoxLayout(self)
|
||
self.layout.setContentsMargins(20, 20, 20, 20)
|
||
self.layout.setSpacing(10)
|
||
|
||
self.title = SubtitleLabel("MRobot AI小助手", self)
|
||
self.title.setAlignment(Qt.AlignCenter)
|
||
self.layout.addWidget(self.title)
|
||
|
||
self.chat_display = TextEdit(self)
|
||
self.chat_display.setReadOnly(True)
|
||
|
||
self.layout.addWidget(self.chat_display, stretch=1)
|
||
|
||
input_layout = QHBoxLayout()
|
||
self.input_box = LineEdit(self)
|
||
self.input_box.setPlaceholderText("请输入你的问题...")
|
||
input_layout.addWidget(self.input_box, stretch=1)
|
||
|
||
# self.send_btn = PushButton("发送", self)
|
||
self.send_btn = PushButton("发送", icon=FluentIcon.SEND, parent=self)
|
||
|
||
self.send_btn.setFixedWidth(80)
|
||
input_layout.addWidget(self.send_btn)
|
||
|
||
self.layout.addLayout(input_layout)
|
||
|
||
self.send_btn.clicked.connect(self.send_message)
|
||
self.input_box.returnPressed.connect(self.send_message)
|
||
|
||
self.worker = None
|
||
self.is_waiting = False
|
||
self.history = []
|
||
self.chat_display.setText(
|
||
"<b>MRobot:</b> 欢迎使用MRobot AI小助手!"
|
||
)
|
||
|
||
def send_message(self):
|
||
if self.is_waiting:
|
||
return
|
||
prompt = self.input_box.text().strip()
|
||
if not prompt:
|
||
return
|
||
if len(prompt) > 1000:
|
||
InfoBar.warning(
|
||
title='警告',
|
||
content="每条发送内容不能超过1000字,请精简后再发送。",
|
||
orient=Qt.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.BOTTOM,
|
||
duration=-1,
|
||
parent=self
|
||
)
|
||
return
|
||
if len(self.history) >= self.MAX_HISTORY:
|
||
InfoBar.warning(
|
||
title='警告',
|
||
content="对话条数已达上限,请清理历史或重新开始。",
|
||
orient=Qt.Horizontal,
|
||
isClosable=True,
|
||
position=InfoBarPosition.BOTTOM,
|
||
duration=-1,
|
||
parent=self
|
||
)
|
||
return
|
||
self.append_chat("你", prompt)
|
||
self.input_box.clear()
|
||
self.append_chat("MRobot", "", new_line=False)
|
||
self.is_waiting = True
|
||
|
||
# 只在首次对话时加入身份提示
|
||
if not self.history:
|
||
system_prompt = (
|
||
"你是MRobot,是QUT青岛理工大学机器人战队的AI机器人。"
|
||
"请以此身份与用户进行交流。"
|
||
)
|
||
else:
|
||
system_prompt = ""
|
||
|
||
self.history.append({"role": "user", "content": prompt})
|
||
context = system_prompt + "\n" if system_prompt else ""
|
||
for msg in self.history:
|
||
if msg["role"] == "user":
|
||
context += f"你: {msg['content']}\n"
|
||
else:
|
||
context += f"AI: {msg['content']}\n"
|
||
|
||
self.worker = AIWorker(context)
|
||
self.worker.response_signal.connect(self.stream_response)
|
||
self.worker.done_signal.connect(self.finish_response)
|
||
self.worker.error_signal.connect(self.show_error) # 新增
|
||
self.worker.start()
|
||
|
||
|
||
def append_chat(self, sender, message, new_line=True):
|
||
if new_line:
|
||
self.chat_display.append(f"<b>{sender}:</b> {message}")
|
||
else:
|
||
self.chat_display.append(f"<b>{sender}:</b> ")
|
||
self.chat_display.moveCursor(self.chat_display.textCursor().End)
|
||
# 新增:保存AI回复到历史
|
||
if sender == "AI" and message:
|
||
self.history.append({"role": "ai", "content": message})
|
||
|
||
def stream_response(self, text):
|
||
cursor = self.chat_display.textCursor()
|
||
cursor.movePosition(cursor.End)
|
||
cursor.insertText(text)
|
||
self.chat_display.setTextCursor(cursor)
|
||
# 新增:流式保存AI回复
|
||
if self.history and self.history[-1]["role"] == "ai":
|
||
self.history[-1]["content"] += text
|
||
elif text:
|
||
self.history.append({"role": "ai", "content": text})
|
||
|
||
def finish_response(self):
|
||
self.chat_display.append("") # 换行
|
||
self.is_waiting = False
|
||
|
||
def show_error(self, msg): # 新增
|
||
InfoBar.error(
|
||
title='失败',
|
||
content=msg,
|
||
orient=Qt.Vertical,
|
||
isClosable=True,
|
||
position=InfoBarPosition.TOP,
|
||
duration=-1,
|
||
parent=self
|
||
)
|
||
self.is_waiting = False
|