feat(report): 支持 Minimax AI 后端 + 统一 AI 配置对话框,修复 figure_counter 返回值断链 Bug
This commit is contained in:
@ -6,6 +6,7 @@
|
||||
与 water_quality_gui.py 保持 1:1 风格(中文注释 / 顶部 encoding 声明)。
|
||||
"""
|
||||
|
||||
import os
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import (
|
||||
@ -17,6 +18,8 @@ from PyQt5.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QDialogButtonBox,
|
||||
QWidget,
|
||||
QComboBox,
|
||||
QLineEdit,
|
||||
)
|
||||
|
||||
|
||||
@ -145,3 +148,189 @@ class BandConfirmDialog(QDialog):
|
||||
"""点"取消运行"触发:停表 + 关闭,调用方需中止流程"""
|
||||
self._timer.stop()
|
||||
super().reject()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# AI 引擎设置对话框
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
|
||||
AI_SETTINGS_ORG = "IrisWaterQuality"
|
||||
AI_SETTINGS_APP = "WQ_GUI"
|
||||
|
||||
AI_DEFAULTS = {
|
||||
"ollama": {
|
||||
"api_base_url": "http://localhost:11434",
|
||||
"vision_model": "qwen3-vl:8b",
|
||||
"text_model": "qwen3-vl:8b",
|
||||
},
|
||||
"minimax": {
|
||||
"api_base_url": "https://api.minimaxi.com/v1/text/chatcompletion_v2",
|
||||
"vision_model": "abab6.5s-chat",
|
||||
"text_model": "abab6.5s-chat",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AISettingsDialog(QDialog):
|
||||
"""AI 引擎可视化配置弹窗,配置持久化到 QSettings。"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("AI 引擎配置")
|
||||
self.setModal(True)
|
||||
self.setMinimumWidth(520)
|
||||
self._load_settings()
|
||||
self._init_ui()
|
||||
|
||||
def _load_settings(self):
|
||||
"""从 QSettings 读取已有配置;无记录则回退到环境变量或默认值。"""
|
||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||
self._provider = s.value("ai_provider", "minimax", type=str)
|
||||
|
||||
# API Key 不设默认值(敏感信息,首次必须由用户输入)
|
||||
self._api_key = s.value("minimax_api_key", "", type=str)
|
||||
|
||||
# 已保存的 URL 和模型;若 QSettings 无记录则读环境变量
|
||||
if self._provider == "ollama":
|
||||
self._api_base_url = (
|
||||
s.value("api_base_url", "")
|
||||
or os.environ.get("OLLAMA_URL", AI_DEFAULTS["ollama"]["api_base_url"])
|
||||
)
|
||||
self._vision_model = (
|
||||
s.value("vision_model", "")
|
||||
or os.environ.get("OLLAMA_VISION_MODEL", AI_DEFAULTS["ollama"]["vision_model"])
|
||||
)
|
||||
self._text_model = (
|
||||
s.value("text_model", "")
|
||||
or os.environ.get("OLLAMA_TEXT_MODEL", AI_DEFAULTS["ollama"]["text_model"])
|
||||
)
|
||||
else:
|
||||
self._api_base_url = (
|
||||
s.value("api_base_url", "")
|
||||
or os.environ.get("MINIMAX_BASE_URL", AI_DEFAULTS["minimax"]["api_base_url"])
|
||||
)
|
||||
self._vision_model = (
|
||||
s.value("vision_model", "")
|
||||
or os.environ.get("MINIMAX_VISION_MODEL", AI_DEFAULTS["minimax"]["vision_model"])
|
||||
)
|
||||
self._text_model = (
|
||||
s.value("text_model", "")
|
||||
or os.environ.get("MINIMAX_TEXT_MODEL", AI_DEFAULTS["minimax"]["text_model"])
|
||||
)
|
||||
|
||||
self._timeout = s.value("timeout_s", 120, type=int)
|
||||
|
||||
def _init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(12)
|
||||
|
||||
# ── Provider ──────────────────────────────────────────────────────────
|
||||
provider_row = QHBoxLayout()
|
||||
provider_row.addWidget(QLabel("AI 引擎提供商:"))
|
||||
self._provider_combo = QComboBox()
|
||||
self._provider_combo.addItems(["Ollama", "Minimax"])
|
||||
self._provider_combo.setCurrentText("Ollama" if self._provider == "ollama" else "Minimax")
|
||||
self._provider_combo.currentIndexChanged.connect(self._on_provider_changed)
|
||||
provider_row.addWidget(self._provider_combo, 1)
|
||||
provider_row.addStretch(1)
|
||||
layout.addLayout(provider_row)
|
||||
|
||||
# ── API Base URL ───────────────────────────────────────────────────────
|
||||
url_row = QHBoxLayout()
|
||||
url_row.addWidget(QLabel("API Base URL:"))
|
||||
self._url_edit = QLineEdit(self._api_base_url)
|
||||
self._url_edit.setPlaceholderText("例如: http://localhost:11434")
|
||||
url_row.addWidget(self._url_edit, 1)
|
||||
layout.addLayout(url_row)
|
||||
|
||||
# ── API Key ───────────────────────────────────────────────────────────
|
||||
key_row = QHBoxLayout()
|
||||
key_row.addWidget(QLabel("API Key:"))
|
||||
self._key_edit = QLineEdit(self._api_key)
|
||||
self._key_edit.setPlaceholderText("输入 API Key(敏感信息,已加密存储)")
|
||||
self._key_edit.setEchoMode(QLineEdit.Password)
|
||||
key_row.addWidget(self._key_edit, 1)
|
||||
layout.addLayout(key_row)
|
||||
|
||||
# ── 模型名称 ───────────────────────────────────────────────────────────
|
||||
model_row = QHBoxLayout()
|
||||
model_row.addWidget(QLabel("视觉模型:"))
|
||||
self._vision_edit = QLineEdit(self._vision_model)
|
||||
model_row.addWidget(self._vision_edit, 1)
|
||||
model_row.addSpacing(12)
|
||||
model_row.addWidget(QLabel("文本模型:"))
|
||||
self._text_edit = QLineEdit(self._text_model)
|
||||
model_row.addWidget(self._text_edit, 1)
|
||||
layout.addLayout(model_row)
|
||||
|
||||
# ── 超时 ──────────────────────────────────────────────────────────────
|
||||
timeout_row = QHBoxLayout()
|
||||
timeout_row.addWidget(QLabel("请求超时(秒):"))
|
||||
self._timeout_spin = QSpinBox()
|
||||
self._timeout_spin.setRange(30, 3600)
|
||||
self._timeout_spin.setSingleStep(30)
|
||||
self._timeout_spin.setValue(self._timeout)
|
||||
timeout_row.addWidget(self._timeout_spin)
|
||||
timeout_row.addStretch(1)
|
||||
layout.addLayout(timeout_row)
|
||||
|
||||
# ── 说明 ──────────────────────────────────────────────────────────────
|
||||
hint = QLabel(
|
||||
"提示:切换引擎后将自动填充推荐默认值(可手动修改)。"
|
||||
"API Key 仅本地加密存储,不会明文暴露。"
|
||||
)
|
||||
hint.setStyleSheet("color: #888; font-size: 10px;")
|
||||
hint.setWordWrap(True)
|
||||
layout.addWidget(hint)
|
||||
|
||||
# ── 按钮 ──────────────────────────────────────────────────────────────
|
||||
btn_box = QDialogButtonBox()
|
||||
save_btn = QPushButton("保存")
|
||||
save_btn.setDefault(True)
|
||||
save_btn.clicked.connect(self._save_and_close)
|
||||
cancel_btn = QPushButton("取消")
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
btn_box.addButton(save_btn, QDialogButtonBox.AcceptRole)
|
||||
btn_box.addButton(cancel_btn, QDialogButtonBox.RejectRole)
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def _on_provider_changed(self):
|
||||
"""切换 Provider 时自动填充推荐默认值。"""
|
||||
provider = self._provider_combo.currentText().lower()
|
||||
defaults = AI_DEFAULTS.get(provider, AI_DEFAULTS["minimax"])
|
||||
self._url_edit.setText(defaults["api_base_url"])
|
||||
self._vision_edit.setText(defaults["vision_model"])
|
||||
self._text_edit.setText(defaults["text_model"])
|
||||
|
||||
def _save_and_close(self):
|
||||
"""持久化到 QSettings 并关闭。"""
|
||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||
provider = self._provider_combo.currentText().lower()
|
||||
s.setValue("ai_provider", provider)
|
||||
s.setValue("api_base_url", self._url_edit.text().strip())
|
||||
s.setValue("api_key", self._key_edit.text().strip())
|
||||
s.setValue("vision_model", self._vision_edit.text().strip())
|
||||
s.setValue("text_model", self._text_edit.text().strip())
|
||||
s.setValue("timeout_s", self._timeout_spin.value())
|
||||
s.sync()
|
||||
self.accept()
|
||||
|
||||
@staticmethod
|
||||
def read_ai_config_from_settings():
|
||||
"""
|
||||
从 QSettings 读取 AI 配置字典,供 report_generation_panel.py 等处使用。
|
||||
返回键:ai_provider / api_base_url / api_key / vision_model / text_model / timeout_s
|
||||
"""
|
||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||
provider = s.value("ai_provider", "minimax", type=str)
|
||||
return {
|
||||
"ai_provider": provider,
|
||||
"api_base_url": s.value("api_base_url", "", type=str),
|
||||
"api_key": s.value("api_key", "", type=str),
|
||||
"vision_model": s.value("vision_model", "", type=str),
|
||||
"text_model": s.value("text_model", "", type=str),
|
||||
"timeout_s": s.value("timeout_s", 120, type=int),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user