Feature/delta team/epic r04 #7
+15
-119
@@ -1,6 +1,16 @@
|
||||
"""Settings dialog: AI provider, Sandbox, the unified Connectors (MCP) group
|
||||
(CAD / CAE / MS365 / Other — MCP servers + REST connectors in one place),
|
||||
and the merged "Parameter" group (Cowork / Attachments / GraphRAG caps)."""
|
||||
"""Hộp thoại Cài đặt — khung lắp ráp.
|
||||
|
||||
Năm mục, mỗi mục một trang: Chung, AI Provider, Bảo mật sandbox, Tham số,
|
||||
Auto Model Routing. Bốn mục đầu... đúng hơn: bốn trong năm mục đã bóc sang
|
||||
``presentation/settings/`` (R08-T07); file này còn giữ mục Bảo mật sandbox,
|
||||
phần lắp ráp danh sách mục bên trái, và ``_save`` gọi ``apply_to`` của từng
|
||||
widget con.
|
||||
|
||||
Không còn phần Connector nào ở đây: nó đã dời sang Monitoring → Tools →
|
||||
Connector từ trước. Ngày 25/08 dọn nốt 108 dòng MS365 chết còn sót lại của
|
||||
lần dời đó — năm hàm gọi lẫn nhau, không đường vào, và đọc ba thuộc tính
|
||||
chưa từng được gán nên gọi vào là AttributeError.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
@@ -8,17 +18,13 @@ from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
|
||||
QGroupBox, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
||||
QMessageBox, QPushButton, QScrollArea, QSpinBox, QTreeWidget,
|
||||
QMessageBox, QPushButton, QScrollArea, QSpinBox,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from ..core.ext_connectors import CATEGORIES as EXT_CATEGORIES
|
||||
from ..core.worker import AgentWorker
|
||||
from ..i18n import tr
|
||||
from ..state import AppContext
|
||||
from .icons import icon, IconLabel
|
||||
from .icons import IconLabel
|
||||
from .widgets import ToggleSwitch
|
||||
from .ext_connector_dialog import ExtConnectorEditDialog
|
||||
|
||||
|
||||
from ..presentation.settings.general_settings_widget import GeneralSettingsWidget
|
||||
@@ -133,9 +139,7 @@ class SettingsDialog(QDialog):
|
||||
root.addWidget(self.sandbox_group)
|
||||
|
||||
# Connectors (MCP / REST API) are managed entirely in Monitoring → Tools
|
||||
# → Connector now — no connector UI in Settings. (_ms365_workers is kept
|
||||
# for the dead-but-retained MS365 OAuth sign-in handlers below.)
|
||||
self._ms365_workers = []
|
||||
|
||||
# --- Parameter ---
|
||||
# Đã bóc sang presentation/settings/parameter_settings_widget.py (R08-T07).
|
||||
@@ -249,118 +253,10 @@ class SettingsDialog(QDialog):
|
||||
|
||||
|
||||
# ---- MS365 zero-config sign-in ("connect like Claude") ---------------
|
||||
def _refresh_ms365_status(self) -> None:
|
||||
from ..core.ms365_auth import current_identity
|
||||
who = current_identity(self.ctx.config)
|
||||
if who:
|
||||
self.ms365_status.setText(tr("settings.ms365_signed_in", who=who))
|
||||
self.ms365_signin_btn.setEnabled(False)
|
||||
self.ms365_signout_btn.setEnabled(True)
|
||||
else:
|
||||
self.ms365_status.setText(tr("settings.ms365_signed_out"))
|
||||
self.ms365_signin_btn.setEnabled(True)
|
||||
self.ms365_signout_btn.setEnabled(False)
|
||||
self.ms365_signin_btn.setText(tr("settings.ms365_signin_btn"))
|
||||
self.ms365_signout_btn.setText(tr("settings.ms365_signout_btn"))
|
||||
|
||||
def _ms365_sign_in(self) -> None:
|
||||
from ..core.ms365_auth import current_identity, sign_in
|
||||
self.ms365_signin_btn.setEnabled(False)
|
||||
self.ms365_status.setText(tr("settings.ms365_signing_in"))
|
||||
cfg = self.ctx.config
|
||||
|
||||
def job(worker):
|
||||
# on_code fires (worker thread) with the MSAL device-flow dict —
|
||||
# marshal it to the UI thread via the worker's event signal.
|
||||
return sign_in(lambda flow: worker.event.emit({"device_flow": flow}), cfg)
|
||||
|
||||
def on_event(ev: dict) -> None:
|
||||
if "device_flow" in ev:
|
||||
self._show_ms365_device_code(ev["device_flow"])
|
||||
|
||||
def done(_result) -> None:
|
||||
self._close_ms365_code_dialog()
|
||||
self.ctx.save()
|
||||
self._refresh_ms365_status()
|
||||
QMessageBox.information(
|
||||
self, tr("settings.ms365_signin_btn"),
|
||||
tr("settings.ms365_signed_in", who=current_identity(cfg)))
|
||||
|
||||
def failed(err: str) -> None:
|
||||
self._close_ms365_code_dialog()
|
||||
self._refresh_ms365_status()
|
||||
QMessageBox.warning(self, tr("settings.ms365_signin_btn"), err)
|
||||
|
||||
w = AgentWorker(job)
|
||||
w.event.connect(on_event)
|
||||
w.finished_ok.connect(done)
|
||||
w.failed.connect(failed)
|
||||
self._ms365_workers.append(w)
|
||||
w.start()
|
||||
|
||||
def _close_ms365_code_dialog(self) -> None:
|
||||
dlg = getattr(self, "_ms365_code_dialog", None)
|
||||
if dlg is not None:
|
||||
dlg.close()
|
||||
self._ms365_code_dialog = None
|
||||
|
||||
def _show_ms365_device_code(self, flow: dict) -> None:
|
||||
"""Auto-open the sign-in page + show the one-time code in a COPYABLE,
|
||||
non-modal dialog (so the worker keeps polling and can auto-close it on
|
||||
success). The code is also copied to the clipboard immediately."""
|
||||
import webbrowser
|
||||
|
||||
code = flow.get("user_code", "")
|
||||
url = flow.get("verification_uri", "https://microsoft.com/devicelogin")
|
||||
# Auto-copy the code so the user can just paste it.
|
||||
QGuiApplication.clipboard().setText(code)
|
||||
# Auto-open the browser to the (code-prefilled, if available) sign-in page.
|
||||
try:
|
||||
webbrowser.open(flow.get("verification_uri_complete") or url)
|
||||
except Exception: # noqa: BLE001 — a headless box just shows the link to click
|
||||
pass
|
||||
|
||||
self._close_ms365_code_dialog()
|
||||
dlg = QDialog(self)
|
||||
dlg.setWindowTitle(tr("settings.ms365_signin_btn"))
|
||||
dlg.setMinimumWidth(420)
|
||||
lay = QVBoxLayout(dlg)
|
||||
info = QLabel(tr("settings.ms365_code_hint", url=url))
|
||||
info.setWordWrap(True)
|
||||
info.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextBrowserInteraction)
|
||||
info.setOpenExternalLinks(True)
|
||||
lay.addWidget(info)
|
||||
|
||||
code_row = QHBoxLayout()
|
||||
code_edit = QLineEdit(code)
|
||||
code_edit.setReadOnly(True)
|
||||
f = code_edit.font()
|
||||
f.setPointSize(f.pointSize() + 4)
|
||||
f.setBold(True)
|
||||
code_edit.setFont(f)
|
||||
code_edit.setCursorPosition(0)
|
||||
copy_btn = QPushButton(tr("settings.ms365_copy_code"))
|
||||
copy_btn.setIcon(icon("document"))
|
||||
copy_btn.clicked.connect(lambda: QGuiApplication.clipboard().setText(code))
|
||||
open_btn = QPushButton(tr("settings.ms365_open_link"))
|
||||
open_btn.setIcon(icon("link"))
|
||||
open_btn.clicked.connect(lambda: webbrowser.open(flow.get("verification_uri_complete") or url))
|
||||
code_row.addWidget(code_edit, 1)
|
||||
code_row.addWidget(copy_btn)
|
||||
code_row.addWidget(open_btn)
|
||||
lay.addLayout(code_row)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
buttons.rejected.connect(dlg.reject)
|
||||
lay.addWidget(buttons)
|
||||
|
||||
self._ms365_code_dialog = dlg
|
||||
dlg.show() # non-modal — sign-in polling continues; done() closes it
|
||||
|
||||
def _ms365_sign_out(self) -> None:
|
||||
from ..core.ms365_auth import sign_out_default
|
||||
sign_out_default(self.ctx.config)
|
||||
self._refresh_ms365_status()
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user