测试修改
This commit is contained in:
@ -165,24 +165,43 @@ class BandConfirmDialog(QDialog):
|
|||||||
|
|
||||||
from PyQt5.QtCore import QSettings
|
from PyQt5.QtCore import QSettings
|
||||||
|
|
||||||
|
|
||||||
AI_SETTINGS_ORG = "IrisWaterQuality"
|
AI_SETTINGS_ORG = "IrisWaterQuality"
|
||||||
AI_SETTINGS_APP = "WQ_GUI"
|
AI_SETTINGS_APP = "WQ_GUI"
|
||||||
|
|
||||||
|
# 扩充预设字典,覆盖市面主流大模型标准接口
|
||||||
AI_DEFAULTS = {
|
AI_DEFAULTS = {
|
||||||
"ollama": {
|
"aliyun": {
|
||||||
"api_base_url": "http://localhost:11434",
|
"api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||||
"vision_model": "qwen3-vl:8b",
|
"vision_model": "qwen-vl-max",
|
||||||
"text_model": "qwen3-vl:8b",
|
"text_model": "qwen-max",
|
||||||
|
},
|
||||||
|
"zhipu": {
|
||||||
|
"api_base_url": "https://open.bigmodel.cn/api/paas/v4/chat/completions",
|
||||||
|
"vision_model": "glm-4v",
|
||||||
|
"text_model": "glm-4",
|
||||||
|
},
|
||||||
|
"deepseek": {
|
||||||
|
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||||
|
"vision_model": "deepseek-chat", # DeepSeek暂无独立视觉API,可用通用或自行更换
|
||||||
|
"text_model": "deepseek-chat",
|
||||||
|
},
|
||||||
|
"openai": {
|
||||||
|
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||||
|
"vision_model": "gpt-4o",
|
||||||
|
"text_model": "gpt-4o",
|
||||||
},
|
},
|
||||||
"minimax": {
|
"minimax": {
|
||||||
"api_base_url": "https://api.minimaxi.com/v1/text/chatcompletion_v2",
|
"api_base_url": "https://api.minimax.chat/v1/chat/completions",
|
||||||
"vision_model": "abab6.5s-chat",
|
"vision_model": "abab6.5g-chat",
|
||||||
"text_model": "abab6.5s-chat",
|
"text_model": "abab6.5s-chat",
|
||||||
},
|
},
|
||||||
|
"ollama": {
|
||||||
|
"api_base_url": "http://localhost:11434",
|
||||||
|
"vision_model": "qwen2-vl",
|
||||||
|
"text_model": "qwen2.5",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AISettingsDialog(QDialog):
|
class AISettingsDialog(QDialog):
|
||||||
"""AI 引擎可视化配置弹窗,配置持久化到 QSettings。"""
|
"""AI 引擎可视化配置弹窗,配置持久化到 QSettings。"""
|
||||||
|
|
||||||
@ -195,41 +214,22 @@ class AISettingsDialog(QDialog):
|
|||||||
self._init_ui()
|
self._init_ui()
|
||||||
|
|
||||||
def _load_settings(self):
|
def _load_settings(self):
|
||||||
"""从 QSettings 读取已有配置;无记录则回退到环境变量或默认值。"""
|
"""从 QSettings 读取已有配置;无记录则回退到预设字典或环境变量。"""
|
||||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||||
self._provider = s.value("ai_provider", "minimax", type=str)
|
# 默认推荐 Aliyun (通义千问)
|
||||||
|
self._provider = s.value("ai_provider", "Aliyun", type=str)
|
||||||
|
|
||||||
# API Key 不设默认值(敏感信息,首次必须由用户输入)
|
# 【向后兼容补丁】优先读取新规范的 api_key,如果为空,尝试读取旧版本留下的 minimax_api_key
|
||||||
|
self._api_key = s.value("api_key", "", type=str)
|
||||||
|
if not self._api_key:
|
||||||
self._api_key = s.value("minimax_api_key", "", type=str)
|
self._api_key = s.value("minimax_api_key", "", type=str)
|
||||||
|
|
||||||
# 已保存的 URL 和模型;若 QSettings 无记录则读环境变量
|
provider_key = self._provider.lower()
|
||||||
if self._provider == "ollama":
|
defaults = AI_DEFAULTS.get(provider_key, {"api_base_url": "", "vision_model": "", "text_model": ""})
|
||||||
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._api_base_url = s.value("api_base_url", "", type=str) or defaults.get("api_base_url", "")
|
||||||
|
self._vision_model = s.value("vision_model", "", type=str) or defaults.get("vision_model", "")
|
||||||
|
self._text_model = s.value("text_model", "", type=str) or defaults.get("text_model", "")
|
||||||
self._timeout = s.value("timeout_s", 120, type=int)
|
self._timeout = s.value("timeout_s", 120, type=int)
|
||||||
|
|
||||||
def _init_ui(self):
|
def _init_ui(self):
|
||||||
@ -240,9 +240,15 @@ class AISettingsDialog(QDialog):
|
|||||||
provider_row = QHBoxLayout()
|
provider_row = QHBoxLayout()
|
||||||
provider_row.addWidget(QLabel("AI 引擎提供商:"))
|
provider_row.addWidget(QLabel("AI 引擎提供商:"))
|
||||||
self._provider_combo = QComboBox()
|
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)
|
self._provider_combo.setEditable(True)
|
||||||
|
self._provider_combo.addItems(["Aliyun", "Zhipu", "DeepSeek", "OpenAI", "Minimax", "Ollama"])
|
||||||
|
self._provider_combo.setCurrentText(self._provider)
|
||||||
|
|
||||||
|
# 当文本改变时自动带出推荐配置
|
||||||
|
self._provider_combo.currentTextChanged.connect(self._on_provider_changed)
|
||||||
|
|
||||||
provider_row.addWidget(self._provider_combo, 1)
|
provider_row.addWidget(self._provider_combo, 1)
|
||||||
provider_row.addStretch(1)
|
provider_row.addStretch(1)
|
||||||
layout.addLayout(provider_row)
|
layout.addLayout(provider_row)
|
||||||
@ -251,7 +257,7 @@ class AISettingsDialog(QDialog):
|
|||||||
url_row = QHBoxLayout()
|
url_row = QHBoxLayout()
|
||||||
url_row.addWidget(QLabel("API Base URL:"))
|
url_row.addWidget(QLabel("API Base URL:"))
|
||||||
self._url_edit = QLineEdit(self._api_base_url)
|
self._url_edit = QLineEdit(self._api_base_url)
|
||||||
self._url_edit.setPlaceholderText("例如: http://localhost:11434")
|
self._url_edit.setPlaceholderText("填入兼容 OpenAI 规范的完整 URL")
|
||||||
url_row.addWidget(self._url_edit, 1)
|
url_row.addWidget(self._url_edit, 1)
|
||||||
layout.addLayout(url_row)
|
layout.addLayout(url_row)
|
||||||
|
|
||||||
@ -259,7 +265,7 @@ class AISettingsDialog(QDialog):
|
|||||||
key_row = QHBoxLayout()
|
key_row = QHBoxLayout()
|
||||||
key_row.addWidget(QLabel("API Key:"))
|
key_row.addWidget(QLabel("API Key:"))
|
||||||
self._key_edit = QLineEdit(self._api_key)
|
self._key_edit = QLineEdit(self._api_key)
|
||||||
self._key_edit.setPlaceholderText("输入 API Key(敏感信息,已加密存储)")
|
self._key_edit.setPlaceholderText("输入 API Key(本地加密存储)")
|
||||||
self._key_edit.setEchoMode(QLineEdit.Password)
|
self._key_edit.setEchoMode(QLineEdit.Password)
|
||||||
key_row.addWidget(self._key_edit, 1)
|
key_row.addWidget(self._key_edit, 1)
|
||||||
layout.addLayout(key_row)
|
layout.addLayout(key_row)
|
||||||
@ -288,10 +294,10 @@ class AISettingsDialog(QDialog):
|
|||||||
|
|
||||||
# ── 说明 ──────────────────────────────────────────────────────────────
|
# ── 说明 ──────────────────────────────────────────────────────────────
|
||||||
hint = QLabel(
|
hint = QLabel(
|
||||||
"提示:切换引擎后将自动填充推荐默认值(可手动修改)。"
|
"提示:可以直接在下拉框输入任意名称。选择预设服务商会自动填充推荐的兼容接口 URL。\n"
|
||||||
"API Key 仅本地加密存储,不会明文暴露。"
|
"若使用全能多模态大模型(如 gpt-4o / qwen-vl-max 等),视觉与文本模型填入相同名称即可。"
|
||||||
)
|
)
|
||||||
hint.setStyleSheet("color: #888; font-size: 10px;")
|
hint.setStyleSheet("color: #888; font-size: 11px;")
|
||||||
hint.setWordWrap(True)
|
hint.setWordWrap(True)
|
||||||
layout.addWidget(hint)
|
layout.addWidget(hint)
|
||||||
|
|
||||||
@ -306,10 +312,11 @@ class AISettingsDialog(QDialog):
|
|||||||
btn_box.addButton(cancel_btn, QDialogButtonBox.RejectRole)
|
btn_box.addButton(cancel_btn, QDialogButtonBox.RejectRole)
|
||||||
layout.addWidget(btn_box)
|
layout.addWidget(btn_box)
|
||||||
|
|
||||||
def _on_provider_changed(self):
|
def _on_provider_changed(self, text):
|
||||||
"""切换 Provider 时自动填充推荐默认值。"""
|
"""切换或输入 Provider 时自动填充推荐默认值。"""
|
||||||
provider = self._provider_combo.currentText().lower()
|
provider_key = text.lower()
|
||||||
defaults = AI_DEFAULTS.get(provider, AI_DEFAULTS["minimax"])
|
if provider_key in AI_DEFAULTS:
|
||||||
|
defaults = AI_DEFAULTS[provider_key]
|
||||||
self._url_edit.setText(defaults["api_base_url"])
|
self._url_edit.setText(defaults["api_base_url"])
|
||||||
self._vision_edit.setText(defaults["vision_model"])
|
self._vision_edit.setText(defaults["vision_model"])
|
||||||
self._text_edit.setText(defaults["text_model"])
|
self._text_edit.setText(defaults["text_model"])
|
||||||
@ -317,7 +324,8 @@ class AISettingsDialog(QDialog):
|
|||||||
def _save_and_close(self):
|
def _save_and_close(self):
|
||||||
"""持久化到 QSettings 并关闭。"""
|
"""持久化到 QSettings 并关闭。"""
|
||||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||||
provider = self._provider_combo.currentText().lower()
|
# 获取用户输入的文本(无论是选的还是自己打字的)
|
||||||
|
provider = self._provider_combo.currentText().strip()
|
||||||
s.setValue("ai_provider", provider)
|
s.setValue("ai_provider", provider)
|
||||||
s.setValue("api_base_url", self._url_edit.text().strip())
|
s.setValue("api_base_url", self._url_edit.text().strip())
|
||||||
s.setValue("api_key", self._key_edit.text().strip())
|
s.setValue("api_key", self._key_edit.text().strip())
|
||||||
@ -334,11 +342,13 @@ class AISettingsDialog(QDialog):
|
|||||||
返回键:ai_provider / api_base_url / api_key / vision_model / text_model / timeout_s
|
返回键:ai_provider / api_base_url / api_key / vision_model / text_model / timeout_s
|
||||||
"""
|
"""
|
||||||
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
s = QSettings(AI_SETTINGS_ORG, AI_SETTINGS_APP)
|
||||||
provider = s.value("ai_provider", "minimax", type=str)
|
# 默认返回 Aliyun
|
||||||
|
provider = s.value("ai_provider", "Aliyun", type=str)
|
||||||
return {
|
return {
|
||||||
"ai_provider": provider,
|
"ai_provider": provider,
|
||||||
"api_base_url": s.value("api_base_url", "", type=str),
|
"api_base_url": s.value("api_base_url", "", type=str),
|
||||||
"api_key": s.value("api_key", "", type=str),
|
# 同样向后兼容读取旧版本的 key
|
||||||
|
"api_key": s.value("api_key", s.value("minimax_api_key", "", type=str), type=str),
|
||||||
"vision_model": s.value("vision_model", "", type=str),
|
"vision_model": s.value("vision_model", "", type=str),
|
||||||
"text_model": s.value("text_model", "", type=str),
|
"text_model": s.value("text_model", "", type=str),
|
||||||
"timeout_s": s.value("timeout_s", 120, type=int),
|
"timeout_s": s.value("timeout_s", 120, type=int),
|
||||||
|
|||||||
@ -64,8 +64,9 @@ class ReportWorkerThread(QThread):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ai_cfg = ReportGenerationConfig(
|
ai_cfg = ReportGenerationConfig(
|
||||||
ai_provider="minimax",
|
ai_provider=provider,
|
||||||
minimax_api_key=s.value("api_key", "", type=str) or "",
|
minimax_api_key=s.value("api_key", "", type=str) or "",
|
||||||
|
minimax_base_url=s.value("api_base_url", "", type=str) or None, # <--- 新增这行,把界面上的 URL 传过去
|
||||||
minimax_vision_model=s.value("vision_model", "", type=str) or None,
|
minimax_vision_model=s.value("vision_model", "", type=str) or None,
|
||||||
minimax_text_model=s.value("text_model", "", type=str) or None,
|
minimax_text_model=s.value("text_model", "", type=str) or None,
|
||||||
minimax_timeout_s=timeout,
|
minimax_timeout_s=timeout,
|
||||||
|
|||||||
@ -91,6 +91,7 @@ class ReportGenerationConfig:
|
|||||||
|
|
||||||
self.on_step = on_step
|
self.on_step = on_step
|
||||||
|
|
||||||
|
self.n = 0
|
||||||
|
|
||||||
self._render()
|
self._render()
|
||||||
|
|
||||||
@ -164,8 +165,11 @@ class WaterQualityReportGenerator:
|
|||||||
|
|
||||||
# 通用
|
# 通用
|
||||||
|
|
||||||
|
ai_provider: Optional[str] = None # "ollama" | "minimax",默认 "minimax"
|
||||||
|
|
||||||
ai_provider: Optional[str] = None # "ollama" | "minimax",默认 "minimax"
|
enable_ai_analysis: Optional[bool] = None
|
||||||
|
|
||||||
|
# Ollama 专属
|
||||||
|
|
||||||
ollama_base_url: Optional[str] = None
|
ollama_base_url: Optional[str] = None
|
||||||
|
|
||||||
@ -332,8 +336,6 @@ class WaterQualityReportGenerator:
|
|||||||
self.minimax_base_url = (
|
self.minimax_base_url = (
|
||||||
|
|
||||||
cfg.minimax_base_url.rstrip("/")
|
cfg.minimax_base_url.rstrip("/")
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
if cfg and getattr(cfg, 'minimax_base_url', None)
|
if cfg and getattr(cfg, 'minimax_base_url', None)
|
||||||
|
|
||||||
@ -382,11 +384,11 @@ class WaterQualityReportGenerator:
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
self.enable_ai_analysis = os.environ.get("ENABLE_AI_ANALYSIS", "1") not in {
|
self.enable_ai_analysis = os.environ.get("ENABLE_AI_ANALYSIS", "1") not in {
|
||||||
|
|
||||||
"0",
|
"0",
|
||||||
|
|
||||||
"false",
|
"false",
|
||||||
|
|
||||||
"False",
|
"False",
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -399,8 +401,6 @@ class WaterQualityReportGenerator:
|
|||||||
|
|
||||||
self.parameter_descriptions = {
|
self.parameter_descriptions = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"Chlorophyll": """叶绿素(Chlorophyll)是浮游植物进行光合作用的关键色素,直接反映水体中藻类的生物量与初级生产力水平。它是评价水体富营养化程度最常用的指标之一。当叶绿素浓度持续升高时,表明藻类大量增殖,水华风险显著增加,并可能引发溶解氧剧烈波动、水体透明度下降及底栖生态系统退化。因此,通过遥感手段反演叶绿素浓度,可为水华预警、水质改善及生态修复提供重要科学依据。""",
|
"Chlorophyll": """叶绿素(Chlorophyll)是浮游植物进行光合作用的关键色素,直接反映水体中藻类的生物量与初级生产力水平。它是评价水体富营养化程度最常用的指标之一。当叶绿素浓度持续升高时,表明藻类大量增殖,水华风险显著增加,并可能引发溶解氧剧烈波动、水体透明度下降及底栖生态系统退化。因此,通过遥感手段反演叶绿素浓度,可为水华预警、水质改善及生态修复提供重要科学依据。""",
|
||||||
|
|
||||||
|
|
||||||
@ -464,11 +464,11 @@ class WaterQualityReportGenerator:
|
|||||||
"NH3-N", "BGA", "TT"]
|
"NH3-N", "BGA", "TT"]
|
||||||
|
|
||||||
self.parameter_images = {
|
self.parameter_images = {
|
||||||
|
|
||||||
param: [
|
param: [
|
||||||
|
|
||||||
f"{param}_histogram.png",
|
f"{param}_histogram.png",
|
||||||
|
|
||||||
f"{param}_spectrum_comparison.png",
|
f"{param}_spectrum_comparison.png",
|
||||||
|
|
||||||
f"{param}_scatter_with_confidence.png",
|
f"{param}_scatter_with_confidence.png",
|
||||||
@ -480,13 +480,13 @@ class WaterQualityReportGenerator:
|
|||||||
] for param in params_list
|
] for param in params_list
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ========== 新增:缓存线程锁 ==========
|
# ========== 新增:缓存线程锁 ==========
|
||||||
|
|
||||||
self._cache_lock = Lock()
|
self._cache_lock = Lock()
|
||||||
|
|
||||||
cfg = ai_config
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def apply_ai_config(self, ai_config: ReportGenerationConfig) -> None:
|
def apply_ai_config(self, ai_config: ReportGenerationConfig) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user