a57dbc0252
功能: - 多主机管理 (增删改查, 密码/私钥双认证, 导入导出) - 远程终端 (命令执行 + 常用命令快捷栏 + 超时控制) - SFTP 文件浏览 (上传/下载带进度, 新建/删除/重命名) - 实时监控 (CPU/内存/磁盘/网络, 1-10秒可调刷新) - AI Agent (OpenAI 兼容 API, 5 工具自动调用: 命令/指标/列文件/读文件/上传) 技术栈: PyQt5 + paramiko + psutil + requests + PyInstaller 打包: build_windows.bat / build.sh 一键产出 ~57MB 单文件 exe 测试: core 6/6 + UI 3/3 + E2E 6/6 全部通过
136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""
|
||
AI Agent 配置对话框
|
||
支持自定义 OpenAI 兼容 API(OpenAI / DeepSeek / Moonshot / Ollama 等)
|
||
"""
|
||
from PyQt5.QtCore import Qt
|
||
from PyQt5.QtWidgets import (
|
||
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit, QPushButton,
|
||
QComboBox, QTextEdit, QLabel, QDialogButtonBox, QMessageBox, QGroupBox,
|
||
)
|
||
|
||
from core.ai_agent import AIAgent
|
||
from core.manager import load_ai_config, save_ai_config
|
||
|
||
|
||
PRESETS = [
|
||
("OpenAI 官方", "https://api.openai.com/v1", "gpt-4o-mini"),
|
||
("DeepSeek", "https://api.deepseek.com/v1", "deepseek-chat"),
|
||
("Moonshot Kimi", "https://api.moonshot.cn/v1", "moonshot-v1-8k"),
|
||
("通义千问 (DashScope 兼容)", "https://dashscope.aliyuncs.com/compatible-mode/v1", "qwen-turbo"),
|
||
("智谱 GLM (BigModel)", "https://open.bigmodel.cn/api/paas/v4", "glm-4-flash"),
|
||
("Ollama (本地)", "http://127.0.0.1:11434/v1", "qwen2.5:7b"),
|
||
("自定义", "", ""),
|
||
]
|
||
|
||
|
||
class AIConfigDialog(QDialog):
|
||
def __init__(self, agent: AIAgent, parent=None):
|
||
super().__init__(parent)
|
||
self.agent = agent
|
||
self.setWindowTitle("AI Agent 设置")
|
||
self.resize(560, 480)
|
||
self._build()
|
||
|
||
# 用持久化的配置优先,再用 agent 当前值填充
|
||
cfg = load_ai_config()
|
||
self.api_key_edit.setText(cfg.get("api_key", "") or agent.api_key)
|
||
self.base_url_edit.setText(cfg.get("base_url", "") or agent.base_url)
|
||
self.model_edit.setText(cfg.get("model", "") or agent.model)
|
||
self.system_prompt_edit.setPlainText(
|
||
cfg.get("system_prompt", "") or agent.system_prompt
|
||
)
|
||
# 匹配预设
|
||
for i, (_, url, model) in enumerate(PRESETS):
|
||
if url and url == self.base_url_edit.text() and model == self.model_edit.text():
|
||
self.preset_combo.setCurrentIndex(i)
|
||
break
|
||
|
||
def _build(self):
|
||
layout = QVBoxLayout(self)
|
||
|
||
# 预设
|
||
preset_box = QGroupBox("服务商预设")
|
||
pv = QVBoxLayout(preset_box)
|
||
row = QHBoxLayout()
|
||
row.addWidget(QLabel("快速选择:"))
|
||
self.preset_combo = QComboBox()
|
||
for name, _, _ in PRESETS:
|
||
self.preset_combo.addItem(name)
|
||
self.preset_combo.currentIndexChanged.connect(self._on_preset_changed)
|
||
row.addWidget(self.preset_combo, 1)
|
||
pv.addLayout(row)
|
||
layout.addWidget(preset_box)
|
||
|
||
# 表单
|
||
form_box = QGroupBox("API 配置")
|
||
form = QFormLayout(form_box)
|
||
self.api_key_edit = QLineEdit()
|
||
self.api_key_edit.setEchoMode(QLineEdit.Password)
|
||
self.api_key_edit.setPlaceholderText("sk-...")
|
||
form.addRow("API Key:", self.api_key_edit)
|
||
self.base_url_edit = QLineEdit()
|
||
self.base_url_edit.setPlaceholderText("https://api.openai.com/v1")
|
||
form.addRow("Base URL:", self.base_url_edit)
|
||
self.model_edit = QLineEdit()
|
||
self.model_edit.setPlaceholderText("gpt-4o-mini")
|
||
form.addRow("Model:", self.model_edit)
|
||
layout.addWidget(form_box)
|
||
|
||
# 系统提示
|
||
sp_box = QGroupBox("系统提示词(可自定义 AI 行为)")
|
||
sv = QVBoxLayout(sp_box)
|
||
self.system_prompt_edit = QTextEdit()
|
||
self.system_prompt_edit.setMaximumHeight(120)
|
||
sv.addWidget(self.system_prompt_edit)
|
||
layout.addWidget(sp_box)
|
||
|
||
# 按钮
|
||
bb = QDialogButtonBox()
|
||
self.btn_test = bb.addButton("测试连接", QDialogButtonBox.ActionRole)
|
||
self.btn_test.clicked.connect(self._test)
|
||
bb.addButton(QDialogButtonBox.Save).clicked.connect(self._save)
|
||
bb.addButton(QDialogButtonBox.Cancel).clicked.connect(self.reject)
|
||
layout.addWidget(bb)
|
||
|
||
def _on_preset_changed(self, idx: int):
|
||
name, url, model = PRESETS[idx]
|
||
if url:
|
||
self.base_url_edit.setText(url)
|
||
self.model_edit.setText(model)
|
||
|
||
def _save(self):
|
||
api_key = self.api_key_edit.text().strip()
|
||
base_url = self.base_url_edit.text().strip()
|
||
model = self.model_edit.text().strip()
|
||
system_prompt = self.system_prompt_edit.toPlainText().strip()
|
||
if not base_url or not model:
|
||
QMessageBox.warning(self, "保存失败", "Base URL 和 Model 必填")
|
||
return
|
||
self.agent.update_config(api_key, base_url, model, system_prompt)
|
||
save_ai_config({
|
||
"api_key": api_key, "base_url": base_url,
|
||
"model": model, "system_prompt": system_prompt,
|
||
})
|
||
self.accept()
|
||
|
||
def _test(self):
|
||
# 保存到 agent 临时测一下
|
||
api_key = self.api_key_edit.text().strip()
|
||
base_url = self.base_url_edit.text().strip()
|
||
model = self.model_edit.text().strip()
|
||
if not api_key or not base_url or not model:
|
||
QMessageBox.warning(self, "提示", "请先填好 API Key / Base URL / Model")
|
||
return
|
||
# 保存一份用于测试的临时 agent
|
||
tmp = AIAgent(api_key=api_key, base_url=base_url, model=model)
|
||
try:
|
||
from PyQt5.QtWidgets import QApplication
|
||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||
try:
|
||
tmp.chat("ping", lambda n, a: "ok")
|
||
finally:
|
||
QApplication.restoreOverrideCursor()
|
||
QMessageBox.information(self, "成功", "连接成功!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "失败", f"连接失败:\n{e}")
|