This commit is contained in:
Your Name
2026-10-17 16:51:48 +08:00
parent 56f85f7d89
commit ba2e2e4087
41 changed files with 2851 additions and 96 deletions
@@ -52,6 +52,7 @@ from PySide6.QtWidgets import (
from .. import icons, motion
from ..dialogs.issued_prescription_ai import (
PrescriptionAiStatisticsDialog,
agreement_safe_batch,
agreement_text,
batch_running,
can_open_issued_ai,
@@ -1007,6 +1008,10 @@ class PrescriptionsPage(QWidget):
self._mutation_pending = False
self._ai_statuses: dict[int, dict[str, Any]] = {}
self._ai_enabled = False
self._agreement_visible = False
self._capability_agreement_visible = False
self._ai_capability_token = 0
self._ai_capability_pending = False
self._ai_request_token = 0
self._ai_pending = False
self._ai_timer = QTimer(self)
@@ -1048,7 +1053,7 @@ class PrescriptionsPage(QWidget):
self.orders_button.clicked.connect(lambda: self._open_orders())
header.add_action(self.orders_button)
self.ai_statistics_button = QPushButton("AI 一致度统计", header)
self.ai_statistics_button.setVisible(has_permission(permissions, "tcm.prescriptionAi/statistics", default=False) and callable(getattr(repository, "prescription_ai_statistics", None)))
self.ai_statistics_button.hide()
self.ai_statistics_button.clicked.connect(self._open_ai_statistics)
header.add_action(self.ai_statistics_button)
self.add_button = QPushButton("新增处方", header)
@@ -1437,6 +1442,7 @@ class PrescriptionsPage(QWidget):
self._refresh_pending = False
self._generation += 1
generation = self._generation
self._load_ai_capabilities()
page_size = self._page_size
start_time, end_time = self._date_filters()
filters: dict[str, Any] = {
@@ -1656,6 +1662,7 @@ class PrescriptionsPage(QWidget):
return
method = getattr(self.repository, "list_prescription_ai_statuses", None)
if not has_permission(self.permissions, "tcm.prescriptionAi/statuses", default=False):
self._set_ai_columns(False)
self.ai_status_notice.setText("AI 分析:当前账号没有查看分析状态的权限,请联系管理员授权。")
return
if not callable(method):
@@ -1687,8 +1694,8 @@ class PrescriptionsPage(QWidget):
def _set_ai_columns(self, enabled: bool) -> None:
changed = enabled != self._ai_enabled
self._ai_enabled = enabled
for column in (11, 12):
self.table.setColumnHidden(column, not enabled)
self.table.setColumnHidden(11, not enabled)
self.table.setColumnHidden(12, not (enabled and self._agreement_visible))
if enabled:
header = self.table.horizontalHeader()
header.moveSection(header.visualIndex(11), header.visualIndex(4) + 1)
@@ -1699,10 +1706,54 @@ class PrescriptionsPage(QWidget):
if changed:
self._decorate_rows()
def _set_agreement_visible(self, visible: bool) -> None:
self._agreement_visible = visible
self.table.setColumnHidden(12, not (self._ai_enabled and visible))
self.ai_statistics_button.setVisible(
visible and has_permission(self.permissions, "tcm.prescriptionAi/statistics", default=False)
and callable(getattr(self.repository, "prescription_ai_statistics", None)))
if not visible:
self._ai_statuses = {key: agreement_safe_batch(row, False) for key, row in self._ai_statuses.items()}
for index in range(self.table.rowCount()):
item = self.table.item(index, 12)
if item is not None:
item.setText("")
item.setToolTip("")
item.setData(Qt.ItemDataRole.AccessibleTextRole, "")
def _load_ai_capabilities(self) -> None:
self._ai_capability_token += 1
token, generation = self._ai_capability_token, self._generation
self._ai_request_token += 1
self._ai_pending = False
self._capability_agreement_visible = False
self._set_agreement_visible(False)
method = getattr(self.repository, "get_prescription_ai_capabilities", None)
self._ai_capability_pending = callable(method)
if not callable(method):
return
run_async(method,
on_success=lambda result: self._ai_capabilities_ready(result, token, generation),
on_error=lambda _error: self._ai_capabilities_ready({}, token, generation))
def _ai_capabilities_ready(self, result: Any, token: int, generation: int) -> None:
if token != self._ai_capability_token or generation != self._generation:
return
self._ai_capability_pending = False
self._capability_agreement_visible = _row_mapping(result).get("agreement_visible") is True
self._set_agreement_visible(self._capability_agreement_visible)
if self._capability_agreement_visible:
# A previous deny discarded scores, including completed cached batches.
# Fetch them again after a fresh grant rather than displaying that cache.
self._ai_statuses.clear()
if self.isVisible() and not self._loading:
self._load_ai_statuses()
def _ai_statuses_ready(self, result: Any, ids: list[int], token: int, generation: int) -> None:
if token != self._ai_request_token or generation != self._generation or not self.isVisible():
return
data = _row_mapping(result)
self._set_agreement_visible(self._capability_agreement_visible and data.get("agreement_visible") is True)
self._set_ai_columns(data.get("enabled") is True)
if not self._ai_enabled:
self._ai_timer.stop()
@@ -1713,15 +1764,17 @@ class PrescriptionsPage(QWidget):
self.ai_status_notice.setToolTip("")
self.ai_report_button.setToolTip("自动分析未启用;仍可查看已保存的历史报告。")
return
self.ai_status_notice.setText("AI 分析已启用:保存手工处方后自动生成两份报告,并显示药味与剂量一致度。")
self.ai_status_notice.setText("AI 分析已启用:保存手工处方后自动生成两份报告。"
+ ("可查看药味与剂量一致度。" if self._agreement_visible else ""))
self.ai_status_notice.setToolTip("")
self.ai_report_button.setToolTip("查看两份 AI 报告、候选处方和逐味对照。")
self.ai_report_button.setToolTip("查看两份 AI 报告和候选处方。"
+ ("可查看逐味对照。" if self._agreement_visible else ""))
for value in ids:
self._ai_statuses[value] = {}
for batch in data.get("items") or []:
value = _int(get_value(batch, "prescription_id"), 0)
if value in ids:
self._ai_statuses[value] = _row_mapping(batch)
self._ai_statuses[value] = agreement_safe_batch(batch, self._agreement_visible and _row_mapping(batch).get("agreement_visible") is True)
self._render_ai_statuses()
if any(batch_running(self._ai_statuses.get(value)) for value in self._visible_ai_ids()):
self._ai_timer.start()
@@ -1741,13 +1794,13 @@ class PrescriptionsPage(QWidget):
if batch is None and not blank:
continue
state = "" if blank else (state_text(batch) if batch else "尚无分析记录")
agreement = "" if blank else agreement_text(batch)
agreement = "" if blank or not self._agreement_visible else agreement_text(batch)
for column, text in ((11, state), (12, agreement)):
item = self.table.item(index, column)
if item.text() != text:
item.setText(text)
changed = True
item.setToolTip("" if blank else status_tooltip(batch))
item.setToolTip("" if blank or column == 12 and not self._agreement_visible else status_tooltip(batch))
item.setData(Qt.ItemDataRole.AccessibleTextRole, text)
if changed:
self.table.resizeRowsToContents()
@@ -1757,6 +1810,7 @@ class PrescriptionsPage(QWidget):
def _ai_statuses_error(self, error: Exception, token: int, generation: int) -> None:
if token == self._ai_request_token and generation == self._generation:
self._ai_timer.stop()
self._set_agreement_visible(False)
self._set_ai_columns(False)
self.ai_status_notice.setText("AI 分析暂不可用:请稍后刷新;持续无法使用时,请联系管理员检查服务。")
self.ai_status_notice.setToolTip(friendly_error(error))
@@ -1781,13 +1835,13 @@ class PrescriptionsPage(QWidget):
if _is_blank_prescription(row):
return
menu = QMenu(self.table)
action = menu.addAction("AI 报告 / 逐味对照")
action = menu.addAction("AI 报告 / 逐味对照" if self._agreement_visible else "AI 报告")
action.triggered.connect(lambda: self._run_row_action(row, self._open_ai_report))
self._ai_context_menu = menu
menu.popup(self.table.viewport().mapToGlobal(position))
def _open_ai_statistics(self) -> None:
if not has_permission(self.permissions, "tcm.prescriptionAi/statistics", default=False):
if not self._agreement_visible or not has_permission(self.permissions, "tcm.prescriptionAi/statistics", default=False):
return
dialog = PrescriptionAiStatisticsDialog(self.repository, self.permissions, self)
dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
@@ -2207,12 +2261,14 @@ class PrescriptionsPage(QWidget):
if self.table.rowCount() == 0 and not self._loading:
self.refresh()
elif not self._loading:
self._load_ai_statuses()
self._load_ai_capabilities()
def hideEvent(self, event: Any) -> None:
self._ai_timer.stop()
self._ai_scroll_timer.stop()
self._ai_request_token += 1
self._ai_capability_token += 1
self._ai_capability_pending = False
self._ai_pending = False
super().hideEvent(event)