from collections.abc import Callable from dataclasses import dataclass from typing import Any from cli.utils import SHELL_TEXT_FORMATTER @dataclass(frozen=True) class DiagnosticPagePolicy: """ TUI 页面诊断策略定义。 :param focus_terms: 页面聚焦词 :param subtitle_builder: 页面摘要构建函数 """ focus_terms: tuple[str, ...] subtitle_builder: Callable[..., str] @dataclass(frozen=True) class DiagnosticPagePolicyRegistry: """ TUI 页面诊断策略注册表。 :param policies: 页面诊断策略映射 """ policies: dict[str, DiagnosticPagePolicy] def get(self, page_key: str) -> DiagnosticPagePolicy: """ 读取指定页面诊断策略。 :param page_key: 页面标识 :return: 页面诊断策略 :raises KeyError: 页面未注册时抛出异常 """ return self.policies[page_key] class TuiDiagnosticService: """ 统一封装 TUI 页面诊断提示与摘要拼装逻辑。 该服务通过页面级诊断策略注册表管理各页面的聚焦词和 subtitle 规则, 供 adapter、dashboard 等对象复用。 :param page_policy_registry: 页面诊断策略注册表 """ def __init__(self, page_policy_registry: DiagnosticPagePolicyRegistry) -> None: """ 初始化诊断服务。 :param page_policy_registry: 页面诊断策略注册表 :return: None """ self.page_policy_registry = page_policy_registry @staticmethod def build_focus_hint(terms: tuple[str, ...]) -> str: """ 将诊断聚焦词渲染为统一提示文本。 :param terms: 聚焦词元组 :return: 提示文本 """ return f'聚焦:{" / ".join(terms)}' def build_page_focus_hint(self, page_key: str) -> str: """ 构建指定页面统一聚焦词。 :param page_key: 页面标识 :return: 聚焦提示 """ return self.build_focus_hint(self.page_policy_registry.get(page_key).focus_terms) def build_database_focus_hint(self) -> str: """ 构建数据库页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('database') def build_ops_focus_hint(self) -> str: """ 构建运维页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('ops') def build_cache_focus_hint(self) -> str: """ 构建缓存页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('cache') def build_app_focus_hint(self) -> str: """ 构建应用页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('app') def build_jobs_focus_hint(self) -> str: """ 构建任务页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('jobs') def build_configs_focus_hint(self) -> str: """ 构建参数配置页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('configs') def build_gen_focus_hint(self) -> str: """ 构建代码生成页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('gen') def build_crypto_focus_hint(self) -> str: """ 构建加密页统一聚焦词。 :return: 聚焦提示 """ return self.build_page_focus_hint('crypto') @staticmethod def _build_app_subtitle( env_payload: dict[str, Any] | None, config_payload: dict[str, Any] | None, doctor_payload: dict[str, Any] | None, routes_payload: dict[str, Any] | None, completion_payload: dict[str, Any] | None, *, focus_hint: str, ) -> str: """ 构建应用页诊断摘要。 :param env_payload: `app env` 结果 :param config_payload: `app config` 结果 :param doctor_payload: `app doctor` 结果 :param routes_payload: `app routes` 结果 :param completion_payload: `completion doctor` 结果 :param focus_hint: 聚焦提示 :return: 页面摘要 """ env_ok = bool(isinstance(env_payload, dict) and env_payload.get('ok', False)) config_ok = bool(isinstance(config_payload, dict) and config_payload.get('ok', False)) doctor_ok = bool(isinstance(doctor_payload, dict) and doctor_payload.get('ok', False)) routes_ok = bool(isinstance(routes_payload, dict) and routes_payload.get('ok', False)) completion_ok = bool(isinstance(completion_payload, dict) and completion_payload.get('ok', False)) route_count = routes_payload.get('count', 0) if isinstance(routes_payload, dict) else 0 if not env_ok or not config_ok: return f'{focus_hint} | 应用基础信息读取异常,优先确认环境映射与配置摘要' if not doctor_ok: return f'{focus_hint} | 启动前检查异常,优先确认数据库、Redis 与加密组件状态' if not completion_ok: return f'{focus_hint} | 补全诊断读取异常,优先确认项目目录与 shell 补全配置' if not routes_ok: return f'{focus_hint} | 路由摘要读取异常,优先确认应用初始化与路由注册状态' return ( f'{focus_hint} | 当前已加载 {route_count} 条注册路由,' '可继续查看环境映射、配置摘要、启动前检查、补全诊断与路由状态' ) @staticmethod def _build_database_subtitle( revision_payload: dict[str, Any] | None, check_payload: dict[str, Any] | None, heads_payload: dict[str, Any] | None, *, focus_hint: str, ) -> str: """ 构建数据库页诊断摘要。 :param revision_payload: `db current` 结果 :param check_payload: `db check` 结果 :param heads_payload: `db heads` 结果 :param focus_hint: 聚焦提示 :return: 页面摘要 """ revision = revision_payload.get('currentRevision', '-') if isinstance(revision_payload, dict) else '-' revision_text = SHELL_TEXT_FORMATTER.truncate_text(str(revision or '-'), 48) revision_ok = bool(isinstance(revision_payload, dict) and revision_payload.get('ok', False)) check_ok = bool(isinstance(check_payload, dict) and check_payload.get('ok', False)) heads_ok = bool(isinstance(heads_payload, dict) and heads_payload.get('ok', False)) heads_items = heads_payload.get('items') if isinstance(heads_payload, dict) else None heads_count = len(heads_items) if isinstance(heads_items, list) else 0 if not revision_ok or not check_ok: return f'{focus_hint} | 数据库异常,优先确认迁移版本、连接状态与 heads 信息' if not heads_ok or heads_count != 1: return f'{focus_hint} | 数据库存在迁移分叉风险,优先确认 heads 和历史版本' return f'{focus_hint} | 数据库基线正常,当前 revision {revision_text},可继续查看连接、heads 和历史版本' @staticmethod def _build_ops_subtitle( health_payload: dict[str, Any] | None, deps_payload: dict[str, Any] | None, server_payload: dict[str, Any] | None, *, focus_hint: str, ) -> str: """ 构建运维页诊断摘要。 :param health_payload: `ops health` 结果 :param deps_payload: `ops deps` 结果 :param server_payload: `ops server-info` 结果 :param focus_hint: 聚焦提示 :return: 页面摘要 """ health_ok = bool(isinstance(health_payload, dict) and health_payload.get('ok', False)) deps_ok = bool(isinstance(deps_payload, dict) and deps_payload.get('ok', False)) server_ok = bool(isinstance(server_payload, dict) and server_payload.get('ok', False)) if not health_ok: return f'{focus_hint} | 运维探活存在异常,优先核对数据库/Redis 连通性与依赖版本' if not deps_ok: return f'{focus_hint} | 运维依赖存在异常,优先核对缺失依赖与版本兼容性' if not server_ok: return f'{focus_hint} | 服务器信息采集异常,优先重新检查主机资源与运行环境' return f'{focus_hint} | 基础探活和依赖状态正常,可继续查看服务器资源与磁盘样本' @staticmethod def _build_cache_subtitle( stats_payload: dict[str, Any] | None, matched_count: int, *, focus_hint: str, ) -> str: """ 构建缓存页诊断摘要。 :param stats_payload: `cache stats` 结果 :param matched_count: 当前筛选后缓存名数量 :param focus_hint: 聚焦提示 :return: 页面摘要 """ db_size = stats_payload.get('dbSize', '-') if isinstance(stats_payload, dict) else '-' client_count = '-' if isinstance(stats_payload, dict) and isinstance(stats_payload.get('info'), dict): client_count = stats_payload['info'].get('connected_clients', '-') # type: ignore[index] return ( f'{focus_hint} | 缓存基线正常,当前已加载 {matched_count} 个缓存名,' f'Redis 键数 {db_size},连接数 {client_count},可继续查看键列表、键值样本和 TTL' ) @staticmethod def _build_jobs_subtitle( filter_label: str, matched_count: int, failed_job_names: set[str], paused_count: int, *, focus_hint: str, ) -> str: """ 构建任务页诊断摘要。 :param filter_label: 当前筛选标签 :param matched_count: 当前匹配任务数 :param failed_job_names: 最近失败任务集合 :param paused_count: 暂停任务数量 :param focus_hint: 聚焦提示 :return: 页面摘要 """ if failed_job_names: return ( f'{focus_hint} | 当前筛选:{filter_label},已匹配 {matched_count} 条任务,' f'失败任务 {len(failed_job_names)} 个,暂停任务 {paused_count} 个' ) return ( f'{focus_hint} | 当前筛选:{filter_label},已匹配 {matched_count} 条任务,' f'当前没有失败任务,暂停任务 {paused_count} 个' ) @staticmethod def _build_configs_subtitle( filter_label: str, matched_count: int, mismatch_count: int, drift_count: int, *, focus_hint: str, ) -> str: """ 构建参数配置页诊断摘要。 :param filter_label: 当前筛选标签 :param matched_count: 当前匹配配置数 :param mismatch_count: 值不一致数量 :param drift_count: 缓存漂移数量 :param focus_hint: 聚焦提示 :return: 页面摘要 """ return ( f'{focus_hint} | 当前筛选:{filter_label},已匹配 {matched_count} 项配置,' f'值不一致 {mismatch_count} 项,缓存漂移 {drift_count} 项' ) @staticmethod def _build_gen_subtitle( matched_count: int, importable_count: int, *, focus_hint: str, ) -> str: """ 构建代码生成页诊断摘要。 :param matched_count: 当前匹配业务表数 :param importable_count: 可导入物理表数 :param focus_hint: 聚焦提示 :return: 页面摘要 """ return ( f'{focus_hint} | 当前已匹配 {matched_count} 张业务表,' f'可导入物理表 {importable_count} 张,可继续查看表定义、预检查和代码预览' ) @staticmethod def _build_crypto_subtitle( validate_payload: dict[str, Any] | None, public_payload: dict[str, Any] | None, *, focus_hint: str, ) -> str: """ 构建加密页诊断摘要。 :param validate_payload: `crypto validate` 结果 :param public_payload: `crypto export-public` 结果 :param focus_hint: 聚焦提示 :return: 页面摘要 """ validate_ok = bool(isinstance(validate_payload, dict) and validate_payload.get('ok', False)) public_ok = bool(isinstance(public_payload, dict) and public_payload.get('ok', False)) public_key_payload = public_payload.get('publicKey') if isinstance(public_payload, dict) else None current_kid = public_key_payload.get('kid', '-') if isinstance(public_key_payload, dict) else '-' supported_kids = public_key_payload.get('supportedKids') if isinstance(public_key_payload, dict) else None supported_count = len(supported_kids) if isinstance(supported_kids, list) else 0 if not validate_ok: return f'{focus_hint} | 运行校验失败,优先确认传输加密配置与环境变量' if not public_ok or not isinstance(public_key_payload, dict): return f'{focus_hint} | 公钥导出结果异常,优先确认当前 KID 与公钥内容' return ( f'{focus_hint} | 当前 KID {current_kid},兼容版本 {supported_count} 个,' '可继续查看公钥身份、兼容版本与轮换预演入口' ) def build_app_diagnostic_subtitle( self, env_payload: dict[str, Any] | None, config_payload: dict[str, Any] | None, doctor_payload: dict[str, Any] | None, routes_payload: dict[str, Any] | None, completion_payload: dict[str, Any] | None, ) -> str: """ 构建应用页统一诊断摘要。 :param env_payload: `app env` 结果 :param config_payload: `app config` 结果 :param doctor_payload: `app doctor` 结果 :param routes_payload: `app routes` 结果 :param completion_payload: `completion doctor` 结果 :return: 页面摘要 """ return self.page_policy_registry.get('app').subtitle_builder( env_payload, config_payload, doctor_payload, routes_payload, completion_payload, focus_hint=self.build_app_focus_hint(), ) def build_database_diagnostic_subtitle( self, revision_payload: dict[str, Any] | None, check_payload: dict[str, Any] | None, heads_payload: dict[str, Any] | None, ) -> str: """ 构建数据库页统一诊断摘要。 :param revision_payload: `db current` 结果 :param check_payload: `db check` 结果 :param heads_payload: `db heads` 结果 :return: 页面摘要 """ return self.page_policy_registry.get('database').subtitle_builder( revision_payload, check_payload, heads_payload, focus_hint=self.build_database_focus_hint(), ) def build_ops_diagnostic_subtitle( self, health_payload: dict[str, Any] | None, deps_payload: dict[str, Any] | None, server_payload: dict[str, Any] | None, ) -> str: """ 构建运维页统一诊断摘要。 :param health_payload: `ops health` 结果 :param deps_payload: `ops deps` 结果 :param server_payload: `ops server-info` 结果 :return: 页面摘要 """ return self.page_policy_registry.get('ops').subtitle_builder( health_payload, deps_payload, server_payload, focus_hint=self.build_ops_focus_hint(), ) def build_cache_diagnostic_subtitle( self, stats_payload: dict[str, Any] | None, matched_count: int, ) -> str: """ 构建缓存页统一诊断摘要。 :param stats_payload: `cache stats` 结果 :param matched_count: 当前筛选后缓存名数量 :return: 页面摘要 """ return self.page_policy_registry.get('cache').subtitle_builder( stats_payload, matched_count, focus_hint=self.build_cache_focus_hint(), ) def build_jobs_diagnostic_subtitle( self, filter_label: str, matched_count: int, failed_job_names: set[str], paused_count: int, ) -> str: """ 构建任务页统一诊断摘要。 :param filter_label: 当前筛选标签 :param matched_count: 当前匹配任务数 :param failed_job_names: 最近失败任务集合 :param paused_count: 暂停任务数量 :return: 页面摘要 """ return self.page_policy_registry.get('jobs').subtitle_builder( filter_label, matched_count, failed_job_names, paused_count, focus_hint=self.build_jobs_focus_hint(), ) def build_configs_diagnostic_subtitle( self, filter_label: str, matched_count: int, mismatch_count: int, drift_count: int, ) -> str: """ 构建参数配置页统一诊断摘要。 :param filter_label: 当前筛选标签 :param matched_count: 当前匹配配置数 :param mismatch_count: 值不一致数量 :param drift_count: 缓存漂移数量 :return: 页面摘要 """ return self.page_policy_registry.get('configs').subtitle_builder( filter_label, matched_count, mismatch_count, drift_count, focus_hint=self.build_configs_focus_hint(), ) def build_gen_diagnostic_subtitle( self, matched_count: int, importable_count: int, ) -> str: """ 构建代码生成页统一诊断摘要。 :param matched_count: 当前匹配业务表数 :param importable_count: 可导入物理表数 :return: 页面摘要 """ return self.page_policy_registry.get('gen').subtitle_builder( matched_count, importable_count, focus_hint=self.build_gen_focus_hint(), ) def build_crypto_diagnostic_subtitle( self, validate_payload: dict[str, Any] | None, public_payload: dict[str, Any] | None, ) -> str: """ 构建加密页统一诊断摘要。 :param validate_payload: `crypto validate` 结果 :param public_payload: `crypto export-public` 结果 :return: 页面摘要 """ return self.page_policy_registry.get('crypto').subtitle_builder( validate_payload, public_payload, focus_hint=self.build_crypto_focus_hint(), ) TUI_DIAGNOSTIC_PAGE_POLICY_REGISTRY = DiagnosticPagePolicyRegistry( policies={ 'database': DiagnosticPagePolicy( focus_terms=('迁移版本', '连接状态', 'Heads'), subtitle_builder=TuiDiagnosticService._build_database_subtitle, ), 'ops': DiagnosticPagePolicy( focus_terms=('数据库连通性', 'Redis 连通性', '依赖版本'), subtitle_builder=TuiDiagnosticService._build_ops_subtitle, ), 'cache': DiagnosticPagePolicy( focus_terms=('Redis 键数', '连接数', '缓存名前缀'), subtitle_builder=TuiDiagnosticService._build_cache_subtitle, ), 'app': DiagnosticPagePolicy( focus_terms=('环境映射', '配置摘要', '启动前检查'), subtitle_builder=TuiDiagnosticService._build_app_subtitle, ), 'jobs': DiagnosticPagePolicy( focus_terms=('失败聚合', '暂停任务', '执行轨迹'), subtitle_builder=TuiDiagnosticService._build_jobs_subtitle, ), 'configs': DiagnosticPagePolicy( focus_terms=('高风险配置', '值不一致', '缓存漂移'), subtitle_builder=TuiDiagnosticService._build_configs_subtitle, ), 'gen': DiagnosticPagePolicy( focus_terms=('生成前校验', '同步预检查', '代码预览'), subtitle_builder=TuiDiagnosticService._build_gen_subtitle, ), 'crypto': DiagnosticPagePolicy( focus_terms=('运行校验', '公钥身份', '兼容版本'), subtitle_builder=TuiDiagnosticService._build_crypto_subtitle, ), } ) TUI_DIAGNOSTIC_SERVICE = TuiDiagnosticService(TUI_DIAGNOSTIC_PAGE_POLICY_REGISTRY)