From e29201f8eca48256eff41b5354684b19b755c3bd Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 3 Nov 2025 15:45:48 +0800 Subject: [PATCH] feat(deploy): add support for i18n Signed-off-by: Hongyu Shi --- src/app/deployment/agent.py | 185 ++++++++++++-------- src/app/deployment/components/env_check.py | 35 ++-- src/app/deployment/components/modes.py | 83 ++++----- src/app/deployment/models.py | 24 ++- src/app/deployment/service.py | 194 +++++++++++---------- src/app/deployment/ui.py | 168 +++++++++--------- src/tool/oi_backend_init.py | 3 +- 7 files changed, 369 insertions(+), 323 deletions(-) diff --git a/src/app/deployment/agent.py b/src/app/deployment/agent.py index 07137eb..81268a2 100644 --- a/src/app/deployment/agent.py +++ b/src/app/deployment/agent.py @@ -23,6 +23,7 @@ import httpx import toml from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger from .models import AgentInitStatus, DeploymentState @@ -405,14 +406,14 @@ class AgentManager: AgentInitStatus: 初始化状态 (SUCCESS/SKIPPED/FAILED) """ - self._report_progress(state, "[bold blue]开始初始化智能体...[/bold blue]", progress_callback) + self._report_progress(state, _("[bold blue]开始初始化智能体...[/bold blue]"), progress_callback) try: # 执行所有初始化步骤 return await self._execute_initialization_steps(state, progress_callback) except Exception: - error_msg = "智能体初始化失败" + error_msg = _("智能体初始化失败") self._report_progress(state, f"[red]{error_msg}[/red]", progress_callback) logger.exception(error_msg) return AgentInitStatus.FAILED @@ -450,14 +451,14 @@ class AgentManager: if default_app_id: self._report_progress( state, - f"[bold green]智能体初始化完成! 默认 App ID: {default_app_id}[/bold green]", + _("[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]").format(app_id=default_app_id), progress_callback, ) logger.info("智能体初始化成功完成,默认 App ID: %s", default_app_id) return AgentInitStatus.SUCCESS # 如果没有创建任何智能体,显示警告并返回成功状态 - self._report_progress(state, "[yellow]未能创建任何智能体[/yellow]", progress_callback) + self._report_progress(state, _("[yellow]未能创建任何智能体[/yellow]"), progress_callback) return AgentInitStatus.SUCCESS def _report_progress( @@ -487,7 +488,10 @@ class AgentManager: if not self.service_dir or not self.service_dir.exists(): self._report_progress( state, - f"[yellow]服务配置目录不存在: {self.service_dir},跳过{operation_name}[/yellow]", + _("[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]").format( + dir=self.service_dir, + operation=operation_name, + ), callback, ) logger.warning("服务配置目录不存在: %s", self.service_dir) @@ -498,7 +502,7 @@ class AgentManager: if not service_files: self._report_progress( state, - f"[yellow]未找到服务配置文件,跳过{operation_name}[/yellow]", + _("[yellow]未找到服务配置文件,跳过{operation}[/yellow]").format(operation=operation_name), callback, ) return None @@ -542,7 +546,7 @@ class AgentManager: file_identifier = service_file.stem self._report_progress( state, - f" [red]处理 {file_identifier} 时发生异常[/red]", + _(" [red]处理 {file} 时发生异常[/red]").format(file=file_identifier), callback, ) logger.exception("处理服务文件时发生异常: %s", service_file) @@ -556,15 +560,15 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> bool: """安装 systemd 服务文件""" - self._report_progress(state, "[cyan]安装 systemd 服务文件...[/cyan]", callback) + self._report_progress(state, _("[cyan]安装 systemd 服务文件...[/cyan]"), callback) # 获取服务文件列表 - service_files = self._get_service_files(state, callback, "服务文件安装") + service_files = self._get_service_files(state, callback, _("服务文件安装")) if service_files is None: return True # 处理所有服务文件 - overall_success, installed_files, failed_files = await self._process_service_files( + _overall_success, installed_files, _failed_files = await self._process_service_files( service_files, state, callback, @@ -578,7 +582,7 @@ class AgentManager: self._report_progress( state, - f"[green]成功安装 {len(installed_files)} 个服务文件[/green]", + _("[green]成功安装 {count} 个服务文件[/green]").format(count=len(installed_files)), callback, ) @@ -597,7 +601,7 @@ class AgentManager: self._report_progress( state, - f" [blue]复制服务文件: {service_name}[/blue]", + _(" [blue]复制服务文件: {name}[/blue]").format(name=service_name), callback, ) @@ -610,12 +614,12 @@ class AgentManager: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate() + _stdout, stderr = await process.communicate() except Exception: self._report_progress( state, - f" [red]复制 {service_name} 时发生异常[/red]", + _(" [red]复制 {name} 时发生异常[/red]").format(name=service_name), callback, ) logger.exception("复制服务文件时发生异常: %s", service_file) @@ -624,7 +628,7 @@ class AgentManager: if process.returncode == 0: self._report_progress( state, - f" [green]{service_name} 复制成功[/green]", + _(" [green]{name} 复制成功[/green]").format(name=service_name), callback, ) logger.info("服务文件复制成功: %s -> %s", service_file, target_path) @@ -633,7 +637,7 @@ class AgentManager: error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f" [red]{service_name} 复制失败: {error_output}[/red]", + _(" [red]{name} 复制失败: {error}[/red]").format(name=service_name, error=error_output), callback, ) logger.error("服务文件复制失败: %s, 错误: %s", service_file, error_output) @@ -645,7 +649,7 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> bool: """重新加载 systemd 配置""" - self._report_progress(state, "[cyan]重新加载 systemd 配置...[/cyan]", callback) + self._report_progress(state, _("[cyan]重新加载 systemd 配置...[/cyan]"), callback) try: cmd = "sudo systemctl daemon-reload" @@ -655,12 +659,12 @@ class AgentManager: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate() + _stdout, stderr = await process.communicate() except Exception: self._report_progress( state, - "[red]重新加载 systemd 配置时发生异常[/red]", + _("[red]重新加载 systemd 配置时发生异常[/red]"), callback, ) logger.exception("重新加载 systemd 配置时发生异常") @@ -669,7 +673,7 @@ class AgentManager: if process.returncode == 0: self._report_progress( state, - "[green]systemd 配置重新加载成功[/green]", + _("[green]systemd 配置重新加载成功[/green]"), callback, ) logger.info("systemd 配置重新加载成功") @@ -678,7 +682,7 @@ class AgentManager: error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f"[red]systemd 配置重新加载失败: {error_output}[/red]", + _("[red]systemd 配置重新加载失败: {error}[/red]").format(error=error_output), callback, ) logger.error("systemd 配置重新加载失败: %s", error_output) @@ -690,12 +694,12 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> bool: """运行脚本拉起 MCP Server 进程""" - self._report_progress(state, "[cyan]启动 MCP Server 进程...[/cyan]", callback) + self._report_progress(state, _("[cyan]启动 MCP Server 进程...[/cyan]"), callback) if not self.run_script_path or not self.run_script_path.exists(): self._report_progress( state, - f"[red]MCP 启动脚本不存在: {self.run_script_path}[/red]", + _("[red]MCP 启动脚本不存在: {path}[/red]").format(path=self.run_script_path), callback, ) logger.error("MCP 启动脚本不存在: %s", self.run_script_path) @@ -706,7 +710,7 @@ class AgentManager: # 清理失败不会阻止继续执行,只是记录警告 self._report_progress( state, - "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]", + _("[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]"), callback, ) @@ -714,7 +718,7 @@ class AgentManager: try: # 执行 run.sh 脚本 cmd = f"bash {self.run_script_path}" - self._report_progress(state, f" [blue]执行命令: {cmd}[/blue]", callback) + self._report_progress(state, _(" [blue]执行命令: {cmd}[/blue]").format(cmd=cmd), callback) logger.info("执行 MCP 启动脚本: %s", cmd) process = await asyncio.create_subprocess_shell( @@ -723,27 +727,27 @@ class AgentManager: stderr=asyncio.subprocess.STDOUT, ) - stdout, _ = await process.communicate() + stdout, _stderr = await process.communicate() output = stdout.decode("utf-8") if stdout else "" if process.returncode == 0: self._report_progress( state, - "[green]MCP Server 启动脚本执行成功[/green]", + _("[green]MCP Server 启动脚本执行成功[/green]"), callback, ) logger.info("MCP Server 启动脚本执行成功") return True except Exception: - error_msg = "执行 MCP Server 启动脚本失败" + error_msg = _("执行 MCP Server 启动脚本失败") self._report_progress(state, f"[red]{error_msg}[/red]", callback) logger.exception(error_msg) return False else: self._report_progress( state, - f"[red]MCP Server 启动脚本执行失败 (返回码: {process.returncode})[/red]", + _("[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]").format(code=process.returncode), callback, ) logger.error("MCP Server 启动脚本执行失败: %s, 输出: %s", cmd, output) @@ -801,15 +805,15 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> bool: """验证 MCP Server 服务状态""" - self._report_progress(state, "[cyan]验证 MCP Server 服务状态...[/cyan]", callback) + self._report_progress(state, _("[cyan]验证 MCP Server 服务状态...[/cyan]"), callback) # 获取服务文件列表 - service_files = self._get_service_files(state, callback, "服务验证") + service_files = self._get_service_files(state, callback, _("服务验证")) if service_files is None: return True # 处理所有服务文件 - overall_success, active_services, failed_services = await self._process_service_files( + _overall_success, _active_services, failed_services = await self._process_service_files( service_files, state, callback, @@ -819,13 +823,13 @@ class AgentManager: if failed_services: self._report_progress( state, - f"[red]关键服务状态异常: {', '.join(failed_services)},停止初始化[/red]", + _("[red]关键服务状态异常: {services},停止初始化[/red]").format(services=", ".join(failed_services)), callback, ) logger.error("关键服务状态异常,停止初始化: %s", failed_services) return False - self._report_progress(state, "[green]MCP Server 服务验证完成[/green]", callback) + self._report_progress(state, _("[green]MCP Server 服务验证完成[/green]"), callback) return True async def _verify_single_service( @@ -845,7 +849,7 @@ class AgentManager: if retry_count > max_retries: self._report_progress( state, - f" [red]{service_name} 启动超时 (30秒)[/red]", + _(" [red]{name} 启动超时 (30秒)[/red]").format(name=service_name), callback, ) logger.error("服务启动超时: %s", service_name) @@ -854,13 +858,16 @@ class AgentManager: if retry_count == 0: self._report_progress( state, - f" [magenta]检查服务状态: {service_name}[/magenta]", + _(" [magenta]检查服务状态: {name}[/magenta]").format(name=service_name), callback, ) else: self._report_progress( state, - f" [dim]{service_name} 重新检查状态... (第 {retry_count} 次)[/dim]", + _(" [dim]{name} 重新检查状态... (第 {count} 次)[/dim]").format( + name=service_name, + count=retry_count, + ), callback, ) @@ -873,13 +880,13 @@ class AgentManager: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate() + stdout, _stderr = await process.communicate() output = stdout.decode("utf-8") if stdout else "" except Exception: self._report_progress( state, - f" [red]检查 {service_name} 状态失败[/red]", + _(" [red]检查 {name} 状态失败[/red]").format(name=service_name), callback, ) logger.exception("检查服务状态失败: %s", service_name) @@ -890,7 +897,7 @@ class AgentManager: # 服务正常运行 self._report_progress( state, - f" [green]{service_name} 状态正常 (active running)[/green]", + _(" [green]{service_name} 状态正常 (active running)[/green]").format(service_name=service_name), callback, ) logger.info("服务状态正常: %s", service_name) @@ -900,7 +907,7 @@ class AgentManager: if "failed" in output.lower() or "code=exited" in output.lower(): self._report_progress( state, - f" [red]{service_name} 服务启动失败[/red]", + _(" [red]{service_name} 服务启动失败[/red]").format(service_name=service_name), callback, ) logger.error("服务启动失败: %s, 详细信息: %s", service_name, output.strip()) @@ -911,7 +918,9 @@ class AgentManager: if retry_count == 0: self._report_progress( state, - f" [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]", + _(" [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]").format( + service_name=service_name, + ), callback, ) logger.info("服务正在启动中,等待启动完成: %s", service_name) @@ -923,7 +932,10 @@ class AgentManager: # 其他状态都认为是异常 self._report_progress( state, - f" [red]{service_name} 状态异常 (返回码: {process.returncode})[/red]", + _(" [red]{service_name} 状态异常 (返回码: {returncode})[/red]").format( + service_name=service_name, + returncode=process.returncode, + ), callback, ) logger.warning("服务状态异常: %s, 返回码: %d, 输出: %s", service_name, process.returncode, output.strip()) @@ -941,7 +953,7 @@ class AgentManager: dict[str, str]: MCP 路径名 -> 服务 ID 的映射 """ - self._report_progress(state, "[cyan]注册 MCP 服务...[/cyan]", callback) + self._report_progress(state, _("[cyan]注册 MCP 服务...[/cyan]"), callback) # 加载 MCP 配置 configs = await self._load_mcp_configs(state, callback) @@ -958,19 +970,23 @@ class AgentManager: mcp_service_mapping[mcp_path_name] = service_id self._report_progress( state, - f" [green]{config.name} 注册成功: {mcp_path_name} -> {service_id}[/green]", + _(" [green]{name} 注册成功: {mcp_path} -> {service_id}[/green]").format( + name=config.name, + mcp_path=mcp_path_name, + service_id=service_id, + ), callback, ) else: self._report_progress( state, - f" [red]MCP 服务 {config.name} 注册失败[/red]", + _(" [red]MCP 服务 {name} 注册失败[/red]").format(name=config.name), callback, ) self._report_progress( state, - f"[green]MCP 服务注册完成,成功 {len(mcp_service_mapping)} 个[/green]", + _("[green]MCP 服务注册完成,成功 {count} 个[/green]").format(count=len(mcp_service_mapping)), callback, ) return mcp_service_mapping @@ -982,7 +998,7 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> str | None: """从配置文件创建智能体""" - self._report_progress(state, "[cyan]读取应用配置并创建智能体...[/cyan]", callback) + self._report_progress(state, _("[cyan]读取应用配置并创建智能体...[/cyan]"), callback) # 读取应用配置 app_configs = await self._load_app_configs(state, callback) @@ -1008,7 +1024,7 @@ class AgentManager: default_app_id = app_id self._report_progress( state, - f" [dim]设置默认智能体: {app_config.name}[/dim]", + _(" [dim]设置默认智能体: {name}[/dim]").format(name=app_config.name), callback, ) self.config_manager.set_default_app(app_id) @@ -1016,12 +1032,12 @@ class AgentManager: if created_agents: self._report_progress( state, - f"[green]成功创建 {len(created_agents)} 个智能体[/green]", + _("[green]成功创建 {count} 个智能体[/green]").format(count=len(created_agents)), callback, ) return default_app_id - self._report_progress(state, "[red]未能创建任何智能体[/red]", callback) + self._report_progress(state, _("[red]未能创建任何智能体[/red]"), callback) return None async def _create_single_agent( @@ -1034,7 +1050,7 @@ class AgentManager: """创建单个智能体""" self._report_progress( state, - f"[magenta]创建智能体: {app_config.name}[/magenta]", + _("[magenta]创建智能体: {name}[/magenta]").format(name=app_config.name), callback, ) @@ -1047,7 +1063,7 @@ class AgentManager: if missing_services: self._report_progress( state, - f" [yellow]缺少 MCP 服务: {', '.join(missing_services)},跳过[/yellow]", + _(" [yellow]缺少 MCP 服务: {services},跳过[/yellow]").format(services=", ".join(missing_services)), callback, ) logger.warning("智能体 %s 缺少 MCP 服务: %s", app_config.name, missing_services) @@ -1056,7 +1072,7 @@ class AgentManager: if not mcp_service_ids: self._report_progress( state, - f" [yellow]智能体 {app_config.name} 没有可用的 MCP 服务,跳过[/yellow]", + _(" [yellow]智能体 {name} 没有可用的 MCP 服务,跳过[/yellow]").format(name=app_config.name), callback, ) return None @@ -1076,7 +1092,7 @@ class AgentManager: except Exception: self._report_progress( state, - f" [red]创建智能体 {app_config.name} 失败[/red]", + _(" [red]创建智能体 {name} 失败[/red]").format(name=app_config.name), callback, ) logger.exception("创建智能体失败: %s", app_config.name) @@ -1084,7 +1100,10 @@ class AgentManager: else: self._report_progress( state, - f" [green]智能体 {app_config.name} 创建成功: {app_id}[/green]", + _(" [green]智能体 {name} 创建成功: {app_id}[/green]").format( + name=app_config.name, + app_id=app_id, + ), callback, ) return app_id @@ -1112,12 +1131,12 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> list[AppConfig]: """加载应用配置""" - self._report_progress(state, "[cyan]加载应用配置文件...[/cyan]", callback) + self._report_progress(state, _("[cyan]加载应用配置文件...[/cyan]"), callback) if not self.app_config_path or not self.app_config_path.exists(): self._report_progress( state, - f"[red]应用配置文件不存在: {self.app_config_path}[/red]", + _("[red]应用配置文件不存在: {path}[/red]").format(path=self.app_config_path), callback, ) logger.error("应用配置文件不存在: %s", self.app_config_path) @@ -1131,7 +1150,7 @@ class AgentManager: if not applications: self._report_progress( state, - "[yellow]配置文件中没有找到应用定义[/yellow]", + _("[yellow]配置文件中没有找到应用定义[/yellow]"), callback, ) logger.warning("配置文件中没有找到应用定义") @@ -1151,21 +1170,21 @@ class AgentManager: except KeyError as e: self._report_progress( state, - f" [red]应用配置缺少必需字段: {e}[/red]", + _(" [red]应用配置缺少必需字段: {field}[/red]").format(field=e), callback, ) logger.exception("应用配置缺少必需字段") continue except Exception: - error_msg = f"加载应用配置文件失败: {self.app_config_path}" + error_msg = _("加载应用配置文件失败: {path}").format(path=self.app_config_path) self._report_progress(state, f"[red]{error_msg}[/red]", callback) logger.exception(error_msg) return [] else: self._report_progress( state, - f"[green]成功加载 {len(app_configs)} 个应用配置[/green]", + _("[green]成功加载 {count} 个应用配置[/green]").format(count=len(app_configs)), callback, ) return app_configs @@ -1176,16 +1195,20 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> list[tuple[Path, McpConfig]]: """加载 MCP 配置""" - self._report_progress(state, "[cyan]加载 MCP 配置文件...[/cyan]", callback) + self._report_progress(state, _("[cyan]加载 MCP 配置文件...[/cyan]"), callback) config_loader = McpConfigLoader(self.mcp_config_dir) configs = config_loader.load_all_configs() if not configs: - self._report_progress(state, "[yellow]未找到 MCP 配置文件[/yellow]", callback) + self._report_progress(state, _("[yellow]未找到 MCP 配置文件[/yellow]"), callback) return [] - self._report_progress(state, f"[green]成功加载 {len(configs)} 个 MCP 配置[/green]", callback) + self._report_progress( + state, + _("[green]成功加载 {count} 个 MCP 配置[/green]").format(count=len(configs)), + callback, + ) return configs async def _process_mcp_service( @@ -1201,7 +1224,7 @@ class AgentManager: if not valid: self._report_progress( state, - f" [red]MCP 服务 {config.name} SSE Endpoint 验证失败[/red]", + _(" [red]MCP 服务 {name} SSE Endpoint 验证失败[/red]").format(name=config.name), callback, ) return None @@ -1217,7 +1240,11 @@ class AgentManager: await self._activate_mcp_service(service_id, config.name, state, callback) except (ApiError, httpx.RequestError, Exception) as e: - self._report_progress(state, f" [red]{config.name} 处理失败: {e}[/red]", callback) + self._report_progress( + state, + _(" [red]{name} 处理失败: {error}[/red]").format(name=config.name, error=e), + callback, + ) logger.exception("MCP 服务 %s 处理失败", config.name) return None @@ -1230,7 +1257,7 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> str: """注册 MCP 服务""" - self._report_progress(state, f" [blue]注册 {config.name}...[/blue]", callback) + self._report_progress(state, _(" [blue]注册 {name}...[/blue]").format(name=config.name), callback) return await self.api_client.register_mcp_service(config) async def _install_and_wait_mcp_service( @@ -1241,12 +1268,16 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> bool: """安装并等待 MCP 服务完成""" - self._report_progress(state, f" [cyan]安装 {config_name} (ID: {service_id})...[/cyan]", callback) + self._report_progress( + state, + _(" [cyan]安装 {name} (ID: {service_id})...[/cyan]").format(name=config_name, service_id=service_id), + callback, + ) await self.api_client.install_mcp_service(service_id) - self._report_progress(state, f" [dim]等待 {config_name} 安装完成...[/dim]", callback) + self._report_progress(state, _(" [dim]等待 {name} 安装完成...[/dim]").format(name=config_name), callback) if not await self.api_client.wait_for_installation(service_id): - self._report_progress(state, f" [red]{config_name} 安装超时[/red]", callback) + self._report_progress(state, _(" [red]{name} 安装超时[/red]").format(name=config_name), callback) return False return True @@ -1259,9 +1290,9 @@ class AgentManager: callback: Callable[[DeploymentState], None] | None, ) -> None: """激活 MCP 服务""" - self._report_progress(state, f" [yellow]激活 {config_name}...[/yellow]", callback) + self._report_progress(state, _(" [yellow]激活 {name}...[/yellow]").format(name=config_name), callback) await self.api_client.activate_mcp_service(service_id) - self._report_progress(state, f" [green]{config_name} 处理完成[/green]", callback) + self._report_progress(state, _(" [green]{name} 处理完成[/green]").format(name=config_name), callback) async def _validate_sse_endpoint( self, @@ -1273,7 +1304,7 @@ class AgentManager: url = config.config.get("url") or "" self._report_progress( state, - f"[magenta]验证 SSE Endpoint: {config.name} -> {url}[/magenta]", + _("[magenta]验证 SSE Endpoint: {name} -> {url}[/magenta]").format(name=config.name, url=url), callback, ) @@ -1286,7 +1317,7 @@ class AgentManager: if await self._try_simple_sse_check(url, config.name, attempt, max_attempts): self._report_progress( state, - f" [green]{config.name} SSE Endpoint 验证通过[/green]", + _(" [green]{name} SSE Endpoint 验证通过[/green]").format(name=config.name), callback, ) logger.info("SSE Endpoint 简单验证成功: %s (尝试 %d 次)", url, attempt) @@ -1296,7 +1327,7 @@ class AgentManager: if await self._try_mcp_initialize_check(url, config.name, attempt, max_attempts): self._report_progress( state, - f" [green]{config.name} SSE Endpoint 验证通过[/green]", + _(" [green]{name} SSE Endpoint 验证通过[/green]").format(name=config.name), callback, ) logger.info("SSE Endpoint MCP 协议验证成功: %s (尝试 %d 次)", url, attempt) @@ -1309,7 +1340,7 @@ class AgentManager: # 所有尝试都失败了 self._report_progress( state, - f" [red]{config.name} SSE Endpoint 验证失败: 30秒内无法连接[/red]", + _(" [red]{name} SSE Endpoint 验证失败: 30秒内无法连接[/red]").format(name=config.name), callback, ) logger.error( diff --git a/src/app/deployment/components/env_check.py b/src/app/deployment/components/env_check.py index af81faa..725fdf4 100644 --- a/src/app/deployment/components/env_check.py +++ b/src/app/deployment/components/env_check.py @@ -18,6 +18,7 @@ from textual.widgets import ( from app.deployment.service import DeploymentService from app.deployment.ui import DeploymentConfigScreen +from i18n.manager import _ if TYPE_CHECKING: from textual.app import ComposeResult @@ -88,20 +89,20 @@ class EnvironmentCheckScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="check-container"): - yield Static("环境检查", classes="check-title") + yield Static(_("环境检查"), classes="check-title") with Horizontal(classes="check-item"): yield Static("", id="os_status", classes="check-status") - yield Static("检查操作系统类型...", id="os_desc", classes="check-description") + yield Static(_("检查操作系统类型..."), id="os_desc", classes="check-description") with Horizontal(classes="check-item"): yield Static("", id="sudo_status", classes="check-status") - yield Static("检查管理员权限...", id="sudo_desc", classes="check-description") + yield Static(_("检查管理员权限..."), id="sudo_desc", classes="check-description") with Horizontal(classes="button-row"): - yield Button("继续配置", id="continue", variant="success", classes="continue-button", disabled=True) - yield Button("返回", id="back", variant="primary", classes="back-button") - yield Button("退出", id="exit", variant="error", classes="exit-button") + yield Button(_("继续配置"), id="continue", variant="success", classes="continue-button", disabled=True) + yield Button(_("返回"), id="back", variant="primary", classes="back-button") + yield Button(_("退出"), id="exit", variant="error", classes="exit-button") async def on_mount(self) -> None: """界面挂载时开始环境检查""" @@ -120,7 +121,7 @@ class EnvironmentCheckScreen(ModalScreen[bool]): self._update_ui_state() except (OSError, RuntimeError) as e: - self.notify(f"环境检查过程中发生异常: {e}", severity="error") + self.notify(_("环境检查过程中发生异常: {error}").format(error=e), severity="error") async def _check_operating_system(self) -> None: """检查操作系统类型""" @@ -133,17 +134,17 @@ class EnvironmentCheckScreen(ModalScreen[bool]): if is_openeuler: os_status.update("[green]✓[/green]") - os_desc.update("操作系统: openEuler (支持)") + os_desc.update(_("操作系统: openEuler (支持)")) else: os_status.update("[red]✗[/red]") - os_desc.update("操作系统: 非 openEuler (不支持)") - self.error_messages.append("仅支持 openEuler 操作系统") + os_desc.update(_("操作系统: 非 openEuler (不支持)")) + self.error_messages.append(_("仅支持 openEuler 操作系统")) except (OSError, RuntimeError) as e: self.check_results["os"] = False self.query_one("#os_status", Static).update("[red]✗[/red]") - self.query_one("#os_desc", Static).update(f"操作系统检查失败: {e}") - self.error_messages.append(f"操作系统检查异常: {e}") + self.query_one("#os_desc", Static).update(_("操作系统检查失败: {error}").format(error=e)) + self.error_messages.append(_("操作系统检查异常: {error}").format(error=e)) async def _check_sudo_privileges(self) -> None: """检查管理员权限""" @@ -156,17 +157,17 @@ class EnvironmentCheckScreen(ModalScreen[bool]): if has_sudo: sudo_status.update("[green]✓[/green]") - sudo_desc.update("管理员权限: 可用") + sudo_desc.update(_("管理员权限: 可用")) else: sudo_status.update("[red]✗[/red]") - sudo_desc.update("管理员权限: 不可用 (需要 sudo)") - self.error_messages.append("需要管理员权限,请确保可以使用 sudo") + sudo_desc.update(_("管理员权限: 不可用 (需要 sudo)")) + self.error_messages.append(_("需要管理员权限,请确保可以使用 sudo")) except (OSError, RuntimeError) as e: self.check_results["sudo"] = False self.query_one("#sudo_status", Static).update("[red]✗[/red]") - self.query_one("#sudo_desc", Static).update(f"权限检查失败: {e}") - self.error_messages.append(f"权限检查异常: {e}") + self.query_one("#sudo_desc", Static).update(_("权限检查失败: {error}").format(error=e)) + self.error_messages.append(_("权限检查异常: {error}").format(error=e)) def _update_ui_state(self) -> None: """更新界面状态""" diff --git a/src/app/deployment/components/modes.py b/src/app/deployment/components/modes.py index 27ed70a..af108a3 100644 --- a/src/app/deployment/components/modes.py +++ b/src/app/deployment/components/modes.py @@ -19,6 +19,7 @@ from textual.widgets import Button, Input, Label, Static from config.manager import ConfigManager from config.model import Backend, ConfigModel +from i18n.manager import _ from log.manager import get_logger from tool.callback_server import CallbackServer from tool.oi_login import get_auth_url @@ -54,7 +55,7 @@ class InitializationModeScreen(ModalScreen[bool]): """ BINDINGS: ClassVar = [ - Binding("escape", "app.quit", "退出"), + Binding("escape", "app.quit", _("退出")), ] def __init__(self) -> None: @@ -64,16 +65,16 @@ class InitializationModeScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="mode-container"): - yield Static("openEuler Intelligence 初始化", classes="mode-title") + yield Static(_("openEuler Intelligence 初始化"), classes="mode-title") yield Static( - "请选择您的初始化方式:", + _("请选择您的初始化方式:"), classes="mode-description", ) with Horizontal(classes="options-row"): # 连接现有服务选项 yield ModeOptionButton( - "连接现有服务\n\n输入现有服务的 URL 和 Token 即可连接使用", + _("连接现有服务\n\n输入现有服务的 URL 和 Token 即可连接使用"), id="connect_existing", classes="mode-option", variant="default", @@ -81,14 +82,14 @@ class InitializationModeScreen(ModalScreen[bool]): # 部署新服务选项 yield ModeOptionButton( - "部署新服务\n\n在本机部署全新的服务环境和配置", + _("部署新服务\n\n在本机部署全新的服务环境和配置"), id="deploy_new", classes="mode-option", variant="default", ) with Horizontal(classes="mode-button-row"): - yield Button("退出", id="exit", variant="error", classes="exit-button") + yield Button(_("退出"), id="exit", variant="error", classes="exit-button") def on_mount(self) -> None: """组件挂载时的处理""" @@ -153,8 +154,8 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): """ BINDINGS: ClassVar = [ - Binding("escape", "back", "返回"), - Binding("ctrl+q", "app.quit", "退出"), + Binding("escape", "back", _("返回")), + Binding("ctrl+q", "app.quit", _("退出")), ] def __init__(self) -> None: @@ -169,52 +170,52 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="connect-container"): - yield Static("连接现有 openEuler Intelligence 服务", classes="connect-title") + yield Static(_("连接现有 openEuler Intelligence 服务"), classes="connect-title") yield Static( - "请输入您的 openEuler Intelligence 服务连接信息:", + _("请输入您的 openEuler Intelligence 服务连接信息:"), classes="connect-description", ) with Horizontal(classes="form-row"): - yield Label("服务 URL:", classes="form-label") + yield Label(_("服务 URL:"), classes="form-label") yield Input( - placeholder="例如:http://your-server:8002", + placeholder=_("例如:http://your-server:8002"), id="service_url", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("访问令牌:", classes="form-label") + yield Label(_("访问令牌:"), classes="form-label") yield Input( - placeholder="可选,您的访问令牌", + placeholder=_("可选,您的访问令牌"), password=True, id="access_token", classes="form-input", ) yield Button( - "获取", + _("获取"), id="get_api_key", variant="primary", classes="get-api-key-button", disabled=True, ) - yield Static("未验证", id="validation_status", classes="validation-status") + yield Static(_("未验证"), id="validation_status", classes="validation-status") yield Static( - "提示:\n" + _("提示:\n" "• 服务 URL 通常以 http:// 或 https:// 开头\n" "• 访问令牌为可选项,如果服务无需认证可留空\n" "• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" "• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" - "• 系统会自动验证连接并保存配置", + "• 系统会自动验证连接并保存配置"), classes="help-text", ) with Horizontal(classes="mode-button-row"): - yield Button("连接并保存", id="connect", variant="success", disabled=True) - yield Button("返回", id="back", variant="primary") - yield Button("退出", id="exit", variant="error") + yield Button(_("连接并保存"), id="connect", variant="success", disabled=True) + yield Button(_("返回"), id="back", variant="primary") + yield Button(_("退出"), id="exit", variant="error") # ==================== 事件处理方法 ==================== @@ -240,7 +241,7 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): # 获取服务 URL url = self.query_one("#service_url", Input).value.strip() if not url: - self.notify("请先输入服务 URL", severity="warning") + self.notify(_("请先输入服务 URL"), severity="warning") return # 禁用按钮,防止重复点击 @@ -249,12 +250,12 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): # 显示状态 status_widget = self.query_one("#validation_status", Static) - status_widget.update("[yellow]正在获取授权 URL...[/yellow]") + status_widget.update(_("[yellow]正在获取授权 URL...[/yellow]")) # 获取授权 URL auth_url, _token = get_auth_url(url) if not auth_url: - status_widget.update("[red]✗ 获取授权 URL 失败[/red]") + status_widget.update(_("[red]✗ 获取授权 URL 失败[/red]")) get_api_key_btn.disabled = False return @@ -263,16 +264,16 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): launcher_url = self.callback_server.start(auth_url) # 更新状态并打开浏览器 - status_widget.update("[yellow]正在打开浏览器登录...[/yellow]") + status_widget.update(_("[yellow]正在打开浏览器登录...[/yellow]")) webbrowser.open(launcher_url) - self.notify("已打开浏览器,请完成登录", severity="information") + self.notify(_("已打开浏览器,请完成登录"), severity="information") # 异步等待登录完成 self.login_task = asyncio.create_task(self._wait_for_login()) except (OSError, RuntimeError, ValueError) as e: self.logger.exception("获取 API Key 失败") - self.notify(f"获取 API Key 失败: {e}", severity="error") + self.notify(_("获取 API Key 失败: {error}").format(error=e), severity="error") try: get_api_key_btn = self.query_one("#get_api_key", Button) get_api_key_btn.disabled = False @@ -283,7 +284,7 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): async def on_connect_pressed(self) -> None: """处理连接按钮点击""" if not self.is_validated: - self.notify("请等待连接验证完成", severity="warning") + self.notify(_("请等待连接验证完成"), severity="warning") return try: @@ -295,11 +296,11 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): await self._save_configuration(url, token) # 显示成功信息 - self.notify("配置已保存,初始化完成!", severity="information") + self.notify(_("配置已保存,初始化完成!"), severity="information") self.app.exit() except (OSError, RuntimeError, ValueError) as e: - self.notify(f"保存配置时发生错误: {e}", severity="error") + self.notify(_("保存配置时发生错误: {error}").format(error=e), severity="error") @on(Button.Pressed, "#back") async def on_back_pressed(self) -> None: @@ -353,7 +354,7 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): connect_button = self.query_one("#connect", Button) # 更新状态为验证中 - status_widget.update("[yellow]验证连接中...[/yellow]") + status_widget.update(_("[yellow]验证连接中...[/yellow]")) connect_button.disabled = True self.is_validated = False @@ -366,16 +367,16 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): is_valid, message = await validate_oi_connection(url, token) if is_valid: - status_widget.update(f"[green]✓ {message}[/green]") + status_widget.update(_("[green]✓ {message}[/green]").format(message=message)) connect_button.disabled = False self.is_validated = True else: - status_widget.update(f"[red]✗ {message}[/red]") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) connect_button.disabled = True self.is_validated = False except (OSError, RuntimeError, ValueError) as e: - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) connect_button.disabled = True self.is_validated = False @@ -391,7 +392,7 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): return # 等待认证结果(超时 5 分钟) - status_widget.update("[yellow]等待登录完成...[/yellow]") + status_widget.update(_("[yellow]等待登录完成...[/yellow]")) auth_result = await asyncio.to_thread( self.callback_server.wait_for_auth, timeout=300, @@ -407,31 +408,31 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): token_input = self.query_one("#access_token", Input) token_input.value = session_id - status_widget.update("[green]✓ 登录成功,已获取 API Key[/green]") + status_widget.update(_("[green]✓ 登录成功,已获取 API Key[/green]")) self.logger.info("浏览器登录成功,已获取 API Key") # 自动触发验证 await self._validate_connection() else: - status_widget.update("[red]✗ 登录失败:未收到 session ID[/red]") + status_widget.update(_("[red]✗ 登录失败:未收到 session ID[/red]")) get_api_key_btn.disabled = False elif result_type == "error": error_desc = auth_result.get("error_description", "未知错误") - status_widget.update(f"[red]✗ 登录失败: {error_desc}[/red]") + status_widget.update(_("[red]✗ 登录失败: {error}[/red]").format(error=error_desc)) get_api_key_btn.disabled = False else: - status_widget.update("[red]✗ 登录失败:未知结果[/red]") + status_widget.update(_("[red]✗ 登录失败:未知结果[/red]")) get_api_key_btn.disabled = False except asyncio.CancelledError: self.logger.info("登录任务被取消") - status_widget.update("[yellow]登录已取消[/yellow]") + status_widget.update(_("[yellow]登录已取消[/yellow]")) get_api_key_btn.disabled = False except (OSError, RuntimeError, ValueError) as e: self.logger.exception("等待登录时发生错误") - status_widget.update(f"[red]✗ 登录异常: {e}[/red]") + status_widget.update(_("[red]✗ 登录异常: {error}[/red]").format(error=e)) get_api_key_btn.disabled = False finally: # 清理回调服务器 diff --git a/src/app/deployment/models.py b/src/app/deployment/models.py index 29d8098..ac351de 100644 --- a/src/app/deployment/models.py +++ b/src/app/deployment/models.py @@ -10,6 +10,7 @@ import re from dataclasses import dataclass, field from enum import Enum +from i18n.manager import _ from tool.validators import APIValidator # 常量定义 @@ -117,7 +118,7 @@ class DeploymentConfig: """ # 检查必要字段是否完整(只要求端点) if not self.llm.endpoint.strip(): - return False, "LLM API 端点不能为空", {} + return False, _("LLM API 端点不能为空"), {} validator = APIValidator() llm_valid, llm_msg, llm_info = await validator.validate_llm_config( @@ -146,7 +147,7 @@ class DeploymentConfig: """ # 检查必要字段是否完整(只要求端点) if not self.embedding.endpoint.strip(): - return False, "Embedding API 端点不能为空", {} + return False, _("Embedding API 端点不能为空"), {} validator = APIValidator() embed_valid, embed_msg, embed_info = await validator.validate_embedding_config( @@ -168,14 +169,14 @@ class DeploymentConfig: """验证基础字段""" errors = [] if not self.server_ip.strip(): - errors.append("服务器 IP 地址不能为空") + errors.append(_("服务器 IP 地址不能为空")) return errors def _validate_llm_fields(self) -> list[str]: """验证 LLM 配置字段""" errors = [] if not self.llm.endpoint.strip(): - errors.append("LLM API 端点不能为空") + errors.append(_("LLM API 端点不能为空")) return errors def _validate_embedding_fields(self) -> list[str]: @@ -195,10 +196,10 @@ class DeploymentConfig: if self.deployment_mode == "light": # 如果用户填了任何 Embedding 字段,则端点必须填写,API Key 和模型名称允许为空 if has_embedding_config and not self.embedding.endpoint.strip(): - errors.append("Embedding API 端点不能为空") + errors.append(_("Embedding API 端点不能为空")) elif not self.embedding.endpoint.strip(): # 全量部署模式下,Embedding 配置是必需的,但只要求端点必填 - errors.append("Embedding API 端点不能为空") + errors.append(_("Embedding API 端点不能为空")) return errors @@ -206,11 +207,16 @@ class DeploymentConfig: """验证数值字段""" errors = [] if self.llm.max_tokens <= 0: - errors.append("LLM max_tokens 必须大于 0") + errors.append(_("LLM max_tokens 必须大于 0")) if not (MIN_TEMPERATURE <= self.llm.temperature <= MAX_TEMPERATURE): - errors.append(f"LLM temperature 必须在 {MIN_TEMPERATURE} 到 {MAX_TEMPERATURE} 之间") + errors.append( + _("LLM temperature 必须在 {min} 到 {max} 之间").format( + min=MIN_TEMPERATURE, + max=MAX_TEMPERATURE, + ), + ) if self.llm.request_timeout <= 0: - errors.append("LLM 请求超时时间必须大于 0") + errors.append(_("LLM 请求超时时间必须大于 0")) return errors diff --git a/src/app/deployment/service.py b/src/app/deployment/service.py index f1872c2..3986fdf 100644 --- a/src/app/deployment/service.py +++ b/src/app/deployment/service.py @@ -18,6 +18,7 @@ import httpx import toml from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger from .agent import AgentManager @@ -62,7 +63,7 @@ class DeploymentResourceManager: return template_path.read_text(encoding="utf-8") except OSError as e: logger.exception("读取模板文件失败 %s", template_path) - msg = f"无法读取模板文件: {template_path}" + msg = _("无法读取模板文件: {path}").format(path=template_path) raise RuntimeError(msg) from e @classmethod @@ -134,11 +135,11 @@ class DeploymentResourceManager: except toml.TomlDecodeError as e: logger.exception("解析 TOML 内容时出错") - msg = f"TOML 格式错误: {e}" + msg = _("TOML 格式错误: {error}").format(error=e) raise ValueError(msg) from e except Exception as e: logger.exception("更新 TOML 配置时发生错误") - msg = f"更新 TOML 配置失败: {e}" + msg = _("更新 TOML 配置失败: {error}").format(error=e) raise RuntimeError(msg) from e @classmethod @@ -184,26 +185,29 @@ class DeploymentService: # 更新状态 if progress_callback: - temp_state.current_step_name = "检查部署依赖" - temp_state.add_log("正在检查部署环境依赖...") + temp_state.current_step_name = _("检查部署依赖") + temp_state.add_log(_("正在检查部署环境依赖...")) progress_callback(temp_state) # 检查操作系统 if not self.detect_openeuler(): - errors.append("仅支持 openEuler 操作系统") + errors.append(_("仅支持 openEuler 操作系统")) return False, errors # 检查 Python 版本兼容性 python_version = sys.version_info current_version = f"{python_version.major}.{python_version.minor}" if python_version < (3, 10) and progress_callback: - temp_state.add_log(f"⚠ 检测到 Python {current_version},低于 3.10 版本将不支持全量部署模式") + warning_msg = _("⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式").format( + version=current_version, + ) + temp_state.add_log(warning_msg) progress_callback(temp_state) # 检查并安装 openeuler-intelligence-installer if not self.resource_manager.check_installer_available(): if progress_callback: - temp_state.add_log("缺少 openeuler-intelligence-installer 包,正在尝试安装...") + temp_state.add_log(_("缺少 openeuler-intelligence-installer 包,正在尝试安装...")) progress_callback(temp_state) success, install_errors = await self._install_intelligence_installer(progress_callback) @@ -213,11 +217,11 @@ class DeploymentService: # 检查 sudo 权限 if not await self.check_sudo_privileges(): - errors.append("需要管理员权限,请确保可以使用 sudo") + errors.append(_("需要管理员权限,请确保可以使用 sudo")) return False, errors if progress_callback: - temp_state.add_log("✓ 部署环境依赖检查完成") + temp_state.add_log(_("✓ 部署环境依赖检查完成")) progress_callback(temp_state) return True, [] @@ -263,17 +267,17 @@ class DeploymentService: # 检查是否低于 3.10 if python_version < (3, 10) and deployment_mode == "full": - return False, ( + return False, _( "当前 openEuler 版本低于 24.03 LTS," - "不支持全量部署模式。请使用轻量部署模式或升级到 openEuler 24.03+ 版本" + "不支持全量部署模式。请使用轻量部署模式或升级到 openEuler 24.03+ 版本", ) except Exception as e: logger.exception("检查 Python 环境版本时发生错误") - return False, f"无法检查 Python 环境: {e}" + return False, _("无法检查 Python 环境: {error}").format(error=e) else: # Python 版本符合要求 - return True, f"Python 环境版本 {current_version} 符合要求" + return True, _("Python 环境版本 {version} 符合要求").format(version=current_version) async def check_sudo_privileges(self) -> bool: """检查 sudo 权限""" @@ -329,8 +333,8 @@ class DeploymentService: logger.exception("部署过程中发生错误") self.state.is_running = False self.state.is_failed = True - self.state.error_message = "部署过程中发生异常" - self.state.add_log("✗ 部署失败") + self.state.error_message = _("部署过程中发生异常") + self.state.add_log(_("✗ 部署失败")) if progress_callback: progress_callback(self.state) @@ -340,7 +344,7 @@ class DeploymentService: # 部署完成,创建全局配置模板供其他用户使用 self.state.is_running = False self.state.is_completed = True - self.state.add_log("✓ openEuler Intelligence 后端部署完成!") + self.state.add_log(_("✓ openEuler Intelligence 后端部署完成!")) # 创建全局配置模板,包含部署时的配置信息 await self._create_global_config_template(config) @@ -378,7 +382,7 @@ class DeploymentService: try: temp_state = DeploymentState() if progress_callback: - temp_state.add_log("正在安装 openeuler-intelligence-installer...") + temp_state.add_log(_("正在安装 openeuler-intelligence-installer...")) progress_callback(temp_state) # 执行安装命令 @@ -389,21 +393,21 @@ class DeploymentService: # 验证安装是否成功 if self.resource_manager.check_installer_available(): if progress_callback: - temp_state.add_log("✓ openeuler-intelligence-installer 安装成功") + temp_state.add_log(_("✓ openeuler-intelligence-installer 安装成功")) progress_callback(temp_state) return True, [] - errors.append("openeuler-intelligence-installer 安装后资源文件仍然缺失") + errors.append(_("openeuler-intelligence-installer 安装后资源文件仍然缺失")) return False, errors - errors.append("安装 openeuler-intelligence-installer 失败") + errors.append(_("安装 openeuler-intelligence-installer 失败")) # 添加安装输出到错误信息 if output_lines: - errors.append("安装输出:") + errors.append(_("安装输出:")) errors.extend(output_lines[-5:]) # 只显示最后5行 except Exception as e: - errors.append(f"安装过程中发生异常: {e}") + errors.append(_("安装过程中发生异常: {error}").format(error=e)) logger.exception("安装 openeuler-intelligence-installer 时发生异常") return False, errors @@ -439,9 +443,9 @@ class DeploymentService: # 如果是全量部署模式,提示用户到网页端完成 Agent 配置 if config.deployment_mode == "full": - self.state.add_log("✓ 基础服务部署完成") - self.state.add_log("请访问网页管理界面完成 Agent 服务配置") - self.state.add_log(f"管理界面地址: http://{config.server_ip}:8080") + self.state.add_log(_("✓ 基础服务部署完成")) + self.state.add_log(_("请访问网页管理界面完成 Agent 服务配置")) + self.state.add_log(_("管理界面地址: http://{ip}:8080").format(ip=config.server_ip)) return True @@ -478,36 +482,36 @@ class DeploymentService: ) -> bool: """检查系统环境和资源""" self.state.current_step = 1 - self.state.current_step_name = "检查系统环境" - self.state.add_log("正在检查系统环境...") + self.state.current_step_name = _("检查系统环境") + self.state.add_log(_("正在检查系统环境...")) if progress_callback: progress_callback(self.state) # 检查操作系统 if not self.detect_openeuler(): - self.state.add_log("✗ 错误: 仅支持 openEuler 操作系统") + self.state.add_log(_("✗ 错误: 仅支持 openEuler 操作系统")) return False - self.state.add_log("✓ 检测到 openEuler 操作系统") + self.state.add_log(_("✓ 检测到 openEuler 操作系统")) # 检查 openEuler & Python 版本是否支持指定的部署模式 python_check_ok, python_msg = self.check_python_version_for_deployment(config.deployment_mode) if not python_check_ok: - self.state.add_log(f"✗ 错误: {python_msg}") + self.state.add_log(_("✗ 错误: {msg}").format(msg=python_msg)) return False # 检查安装器资源 if not self.resource_manager.check_installer_available(): - self.state.add_log("✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失") - self.state.add_log("请先安装: sudo dnf install -y openeuler-intelligence-installer") + self.state.add_log(_("✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失")) + self.state.add_log(_("请先安装: sudo dnf install -y openeuler-intelligence-installer")) return False - self.state.add_log("✓ openeuler-intelligence-installer 资源可用") + self.state.add_log(_("✓ openeuler-intelligence-installer 资源可用")) # 检查权限 if not await self.check_sudo_privileges(): - self.state.add_log("✗ 错误: 需要管理员权限") + self.state.add_log(_("✗ 错误: 需要管理员权限")) return False - self.state.add_log("✓ 具有管理员权限") + self.state.add_log(_("✓ 具有管理员权限")) return True @@ -518,8 +522,8 @@ class DeploymentService: ) -> bool: """设置部署模式""" self.state.current_step = 0 - self.state.current_step_name = "初始化部署配置" - self.state.add_log("正在设置部署模式...") + self.state.current_step_name = _("初始化部署配置") + self.state.add_log(_("正在设置部署模式...")) if progress_callback: progress_callback(self.state) @@ -546,15 +550,16 @@ class DeploymentService: if process.returncode != 0: error_msg = stderr.decode("utf-8", errors="ignore").strip() - self.state.add_log(f"✗ 设置部署模式失败: {error_msg}") + self.state.add_log(_("✗ 设置部署模式失败: {error}").format(error=error_msg)) return False - web_status = "启用" if config.enable_web else "禁用" - rag_status = "启用" if config.enable_rag else "禁用" - self.state.add_log(f"✓ 部署模式设置完成 (Web界面: {web_status}, RAG: {rag_status})") + web_status = _("启用") if config.enable_web else _("禁用") + rag_status = _("启用") if config.enable_rag else _("禁用") + status_msg = _("✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})").format(web=web_status, rag=rag_status) + self.state.add_log(status_msg) except Exception as e: - self.state.add_log(f"✗ 设置部署模式失败: {e}") + self.state.add_log(_("✗ 设置部署模式失败: {error}").format(error=e)) logger.exception("设置部署模式失败") return False @@ -567,17 +572,17 @@ class DeploymentService: ) -> bool: """运行环境检查脚本""" self.state.current_step = 1 - self.state.current_step_name = "检查系统环境" - self.state.add_log("正在执行系统环境检查...") + self.state.current_step_name = _("检查系统环境") + self.state.add_log(_("正在执行系统环境检查...")) if progress_callback: progress_callback(self.state) try: script_path = self.resource_manager.INSTALLER_BASE_PATH / "1-check-env" / "check_env.sh" - return await self._run_script(script_path, "环境检查脚本", progress_callback) + return await self._run_script(script_path, _("环境检查脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 环境检查失败: {e}") + self.state.add_log(_("✗ 环境检查失败: {error}").format(error=e)) logger.exception("环境检查脚本执行失败") return False @@ -588,8 +593,8 @@ class DeploymentService: ) -> bool: """运行依赖安装脚本""" self.state.current_step = 2 - self.state.current_step_name = "安装依赖组件" - self.state.add_log("正在安装 openEuler Intelligence 依赖组件...") + self.state.current_step_name = _("安装依赖组件") + self.state.add_log(_("正在安装 openEuler Intelligence 依赖组件...")) if progress_callback: progress_callback(self.state) @@ -598,9 +603,9 @@ class DeploymentService: script_path = ( self.resource_manager.INSTALLER_BASE_PATH / "2-install-dependency" / "install_openEulerIntelligence.sh" ) - return await self._run_script(script_path, "依赖安装脚本", progress_callback) + return await self._run_script(script_path, _("依赖安装脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 依赖安装失败: {e}") + self.state.add_log(_("✗ 依赖安装失败: {error}").format(error=e)) logger.exception("依赖安装脚本执行失败") return False @@ -611,17 +616,17 @@ class DeploymentService: ) -> bool: """运行配置初始化脚本""" self.state.current_step = 4 - self.state.current_step_name = "初始化配置和服务" - self.state.add_log("正在初始化配置和启动服务...") + self.state.current_step_name = _("初始化配置和服务") + self.state.add_log(_("正在初始化配置和启动服务...")) if progress_callback: progress_callback(self.state) try: script_path = self.resource_manager.INSTALLER_BASE_PATH / "3-install-server" / "init_config.sh" - return await self._run_script(script_path, "配置初始化脚本", progress_callback) + return await self._run_script(script_path, _("配置初始化脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 配置初始化失败: {e}") + self.state.add_log(_("✗ 配置初始化失败: {error}").format(error=e)) logger.exception("配置初始化脚本执行失败") return False @@ -633,7 +638,7 @@ class DeploymentService: ) -> bool: """运行部署脚本""" if not script_path.exists(): - self.state.add_log(f"✗ 脚本文件不存在: {script_path}") + self.state.add_log(_("✗ 脚本文件不存在: {path}").format(path=script_path)) return False try: @@ -671,16 +676,16 @@ class DeploymentService: self._process = None if return_code == 0: - self.state.add_log(f"✓ {script_name}执行成功") + self.state.add_log(_("✓ {name}执行成功").format(name=script_name)) return True except Exception as e: - self.state.add_log(f"✗ 运行{script_name}时发生错误: {e}") + self.state.add_log(_("✗ 运行{name}时发生错误: {error}").format(name=script_name, error=e)) logger.exception("运行脚本失败: %s", script_path) return False else: - self.state.add_log(f"✗ {script_name}执行失败,返回码: {return_code}") + self.state.add_log(_("✗ {name}执行失败,返回码: {code}").format(name=script_name, code=return_code)) return False async def _heartbeat_progress(self, progress_callback: Callable[[DeploymentState], None] | None) -> None: @@ -701,8 +706,8 @@ class DeploymentService: ) -> bool: """生成配置文件""" self.state.current_step = 3 - self.state.current_step_name = "更新配置文件" - self.state.add_log("正在更新配置文件...") + self.state.current_step_name = _("更新配置文件") + self.state.add_log(_("正在更新配置文件...")) if progress_callback: progress_callback(self.state) @@ -710,14 +715,14 @@ class DeploymentService: try: # 更新 env 文件 await self._update_env_file(config) - self.state.add_log("✓ 更新 env 配置文件") + self.state.add_log(_("✓ 更新 env 配置文件")) # 更新 config.toml 文件 await self._update_config_toml(config) - self.state.add_log("✓ 更新 config.toml 配置文件") + self.state.add_log(_("✓ 更新 config.toml 配置文件")) except Exception as e: - self.state.add_log(f"✗ 更新配置文件失败: {e}") + self.state.add_log(_("✗ 更新配置文件失败: {error}").format(error=e)) logger.exception("更新配置文件失败") return False @@ -746,11 +751,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - _, backup_stderr = await backup_process.communicate() + _backup_stdout, backup_stderr = await backup_process.communicate() if backup_process.returncode != 0: error_msg = backup_stderr.decode("utf-8", errors="ignore").strip() - msg = f"备份 env 文件失败: {error_msg}" + msg = _("备份 env 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) # 写入更新后的内容 @@ -761,12 +766,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - - _, write_stderr = await process.communicate(updated_content.encode()) + _write_stdout, write_stderr = await process.communicate(updated_content.encode()) if process.returncode != 0: error_msg = write_stderr.decode("utf-8", errors="ignore").strip() - msg = f"写入 env 文件失败: {error_msg}" + msg = _("写入 env 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) async def _update_config_toml(self, config: DeploymentConfig) -> None: @@ -792,11 +796,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - _, backup_stderr = await backup_process.communicate() + _backup_stdout, backup_stderr = await backup_process.communicate() if backup_process.returncode != 0: error_msg = backup_stderr.decode("utf-8", errors="ignore").strip() - msg = f"备份 config.toml 文件失败: {error_msg}" + msg = _("备份 config.toml 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) # 写入更新后的内容 @@ -807,12 +811,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - - _, write_stderr = await process.communicate(updated_content.encode()) + _write_stdout, write_stderr = await process.communicate(updated_content.encode()) if process.returncode != 0: error_msg = write_stderr.decode("utf-8", errors="ignore").strip() - msg = f"写入 config.toml 文件失败: {error_msg}" + msg = _("写入 config.toml 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) async def _read_process_output_lines(self, process: asyncio.subprocess.Process) -> AsyncGenerator[str, None]: @@ -860,7 +863,10 @@ class DeploymentService: check_interval = 2.0 # 2秒 for attempt in range(1, max_attempts + 1): - self.state.add_log(f"检查 oi-runtime 服务状态 ({attempt}/{max_attempts})...") + self.state.add_log(_("检查 oi-runtime 服务状态 ({current}/{total})...").format( + current=attempt, + total=max_attempts, + )) if progress_callback: progress_callback(self.state) @@ -879,21 +885,21 @@ class DeploymentService: status = stdout.decode("utf-8").strip() if process.returncode == 0 and status == "active": - self.state.add_log("✓ Framework 服务状态正常") + self.state.add_log(_("✓ Framework 服务状态正常")) return True - self.state.add_log(f"Framework 服务状态: {status}") + self.state.add_log(_("Framework 服务状态: {status}").format(status=status)) if attempt < max_attempts: - self.state.add_log(f"等待 {check_interval} 秒后重试...") + self.state.add_log(_("等待 {seconds} 秒后重试...").format(seconds=check_interval)) await asyncio.sleep(check_interval) except (OSError, TimeoutError) as e: - self.state.add_log(f"检查服务状态时发生错误: {e}") + self.state.add_log(_("检查服务状态时发生错误: {error}").format(error=e)) if attempt < max_attempts: await asyncio.sleep(check_interval) - self.state.add_log("✗ Framework 服务状态检查超时失败") + self.state.add_log(_("✗ Framework 服务状态检查超时失败")) return False async def _check_framework_api_health( @@ -908,7 +914,7 @@ class DeploymentService: api_url = f"http://{server_ip}:{server_port}/api/user" http_ok = 200 # HTTP OK 状态码 - self.state.add_log("等待 openEuler Intelligence 服务就绪") + self.state.add_log(_("等待 openEuler Intelligence 服务就绪")) async with httpx.AsyncClient(timeout=httpx.Timeout(5.0)) as client: for attempt in range(1, max_attempts + 1): @@ -920,20 +926,20 @@ class DeploymentService: response = await client.get(api_url) if response.status_code == http_ok: - self.state.add_log("✓ openEuler Intelligence 服务已就绪") + self.state.add_log(_("✓ openEuler Intelligence 服务已就绪")) return True except httpx.ConnectError: pass except httpx.TimeoutException: - self.state.add_log(f"连接 {api_url} 超时") + self.state.add_log(_("连接 {url} 超时").format(url=api_url)) except (httpx.RequestError, OSError) as e: - self.state.add_log(f"API 连通性检查时发生错误: {e}") + self.state.add_log(_("API 连通性检查时发生错误: {error}").format(error=e)) if attempt < max_attempts: await asyncio.sleep(check_interval) - self.state.add_log("✗ openEuler Intelligence API 服务检查超时失败") + self.state.add_log(_("✗ openEuler Intelligence API 服务检查超时失败")) return False async def _run_agent_init( @@ -943,8 +949,8 @@ class DeploymentService: ) -> bool: """运行 Agent 初始化脚本""" self.state.current_step = 5 - self.state.current_step_name = "初始化 Agent 服务" - self.state.add_log("正在检查 openEuler Intelligence 后端服务状态...") + self.state.current_step_name = _("初始化 Agent 服务") + self.state.add_log(_("正在检查 openEuler Intelligence 后端服务状态...")) if progress_callback: progress_callback(self.state) @@ -955,10 +961,10 @@ class DeploymentService: # 检查 openEuler Intelligence 后端服务状态 if not await self._check_framework_service_health(server_ip, server_port, progress_callback): - self.state.add_log("✗ openEuler Intelligence 服务检查失败") + self.state.add_log(_("✗ openEuler Intelligence 服务检查失败")) return False - self.state.add_log("✓ openEuler Intelligence 服务检查通过,开始初始化 Agent...") + self.state.add_log(_("✓ openEuler Intelligence 服务检查通过,开始初始化 Agent...")) if progress_callback: progress_callback(self.state) @@ -968,11 +974,11 @@ class DeploymentService: init_status = await agent_manager.initialize_agents(self.state, progress_callback) if init_status == AgentInitStatus.SUCCESS: - self.state.add_log("✓ Agent 初始化完成") + self.state.add_log(_("✓ Agent 初始化完成")) return True if init_status == AgentInitStatus.SKIPPED: - self.state.add_log("⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行") + self.state.add_log(_("⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行")) return True # 跳过不算失败,继续部署 # FAILED @@ -1011,15 +1017,15 @@ class DeploymentService: success = template_manager.create_global_template() if success: - self.state.add_log("✓ 全局配置模板创建成功,其他用户可正常使用") + self.state.add_log(_("✓ 全局配置模板创建成功,其他用户可正常使用")) logger.info("全局配置模板创建成功,包含部署时的完整配置信息") else: - self.state.add_log("⚠ 全局配置模板创建失败,可能影响其他用户使用") + self.state.add_log(_("⚠ 全局配置模板创建失败,可能影响其他用户使用")) logger.warning("全局配置模板创建失败") except Exception: logger.exception("创建全局配置模板时发生异常") - self.state.add_log("⚠ 配置模板创建异常,可能影响其他用户使用") + self.state.add_log(_("⚠ 配置模板创建异常,可能影响其他用户使用")) def _update_backend_url_config(self, config: DeploymentConfig) -> None: """ diff --git a/src/app/deployment/ui.py b/src/app/deployment/ui.py index e07f1ee..79a4f75 100644 --- a/src/app/deployment/ui.py +++ b/src/app/deployment/ui.py @@ -26,6 +26,7 @@ from textual.widgets import ( ) from app.tui_header import OIHeader +from i18n.manager import _ from .models import DeploymentConfig, DeploymentState, EmbeddingConfig, LLMConfig from .service import DeploymentService @@ -137,18 +138,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): yield OIHeader() with TabbedContent(): - with TabPane("基础配置", id="basic"): + with TabPane(_("基础配置"), id="basic"): yield from self._compose_basic_config() - with TabPane("LLM 配置", id="llm"): + with TabPane(_("LLM 配置"), id="llm"): yield from self._compose_llm_config() - with TabPane("Embedding 配置", id="embedding"): + with TabPane(_("Embedding 配置"), id="embedding"): yield from self._compose_embedding_config() with Horizontal(classes="button-row"): - yield Button("开始部署", id="deploy", variant="success") - yield Button("取消", id="cancel", variant="error") + yield Button(_("开始部署"), id="deploy", variant="success") + yield Button(_("取消"), id="cancel", variant="error") async def on_mount(self) -> None: """界面挂载时初始化状态""" @@ -165,11 +166,11 @@ class DeploymentConfigScreen(ModalScreen[bool]): desc = self.query_one("#deployment_mode_desc", Static) if self.config.deployment_mode == "full": - btn.label = "全量部署" - desc.update("全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。") + btn.label = _("全量部署") + desc.update(_("全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。")) else: - btn.label = "轻量部署" - desc.update("轻量部署:仅部署框架服务,自动初始化 Agent。") + btn.label = _("轻量部署") + desc.update(_("轻量部署:仅部署框架服务,自动初始化 Agent。")) except (ValueError, AttributeError): # 如果 UI 组件还没初始化完成,忽略错误 @@ -185,7 +186,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): # 如果不需要验证 Embedding,显示相应状态 try: embedding_status = self.query_one("#embedding_validation_status", Static) - embedding_status.update("[dim]不需要验证[/dim]") + embedding_status.update(_("[dim]不需要验证[/dim]")) except (ValueError, AttributeError): pass @@ -195,26 +196,26 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_basic_config(self) -> ComposeResult: """组合基础配置组件""" with Vertical(): - yield Static("基础配置", classes="form-label") + yield Static(_("基础配置"), classes="form-label") with Horizontal(classes="form-row"): - yield Label("服务器 IP 地址:", classes="form-label") + yield Label(_("服务器 IP 地址:"), classes="form-label") yield Input( value="127.0.0.1", # 默认为本地地址 - placeholder="例如:127.0.0.1", + placeholder=_("例如:127.0.0.1"), id="server_ip", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("部署模式:", classes="form-label") + yield Label(_("部署模式:"), classes="form-label") # 使用按钮在轻量/全量间切换,按钮文本显示当前选择(不包含括号描述) - yield Button("轻量部署", id="deployment_mode_btn", classes="form-input", variant="primary") + yield Button(_("轻量部署"), id="deployment_mode_btn", classes="form-input", variant="primary") # 描述区域,显示当前部署模式的详细说明 with Horizontal(classes="form-row"): yield Static( - "轻量部署:仅部署框架服务,自动初始化 Agent。", + _("轻量部署:仅部署框架服务,自动初始化 Agent。"), id="deployment_mode_desc", classes="form-input", ) @@ -222,18 +223,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_llm_config(self) -> ComposeResult: """组合 LLM 配置组件""" with Vertical(classes="llm-config-container"): - yield Static("大语言模型配置", classes="form-label") + yield Static(_("大语言模型配置"), classes="form-label") with Horizontal(classes="form-row"): - yield Label("API 端点:", classes="form-label") + yield Label(_("API 端点:"), classes="form-label") yield Input( - placeholder="例如:http://localhost:11434/v1", + placeholder=_("例如:http://localhost:11434/v1"), id="llm_endpoint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 密钥:", classes="form-label") + yield Label(_("API 密钥:"), classes="form-label") yield Input( placeholder="sk-123456", password=True, @@ -242,19 +243,19 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("模型名称:", classes="form-label") + yield Label(_("模型名称:"), classes="form-label") yield Input( - placeholder="例如:deepseek-llm-7b-chat", + placeholder=_("例如:deepseek-llm-7b-chat"), id="llm_model", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("验证状态:", classes="form-label") - yield Static("未验证", id="llm_validation_status", classes="form-input") + yield Label(_("验证状态:"), classes="form-label") + yield Static(_("未验证"), id="llm_validation_status", classes="form-input") with Horizontal(classes="form-row"): - yield Label("最大输出令牌数:", classes="form-label") + yield Label(_("最大输出令牌数:"), classes="form-label") yield Input( value="8192", id="llm_max_tokens", @@ -262,7 +263,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("Temperature:", classes="form-label") + yield Label(_("Temperature:"), classes="form-label") yield Input( value="0.7", id="llm_temperature", @@ -270,7 +271,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("请求超时 (秒):", classes="form-label") + yield Label(_("请求超时 (秒):"), classes="form-label") yield Input( value="300", id="llm_timeout", @@ -280,25 +281,25 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_embedding_config(self) -> ComposeResult: """组合 Embedding 配置组件""" with Vertical(classes="embedding-config-container"): - yield Static("嵌入模型配置", classes="form-label") + yield Static(_("嵌入模型配置"), classes="form-label") # 添加轻量部署说明 yield Static( - "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]", + _("[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]"), id="embedding_mode_hint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 端点:", classes="form-label") + yield Label(_("API 端点:"), classes="form-label") yield Input( - placeholder="例如:http://localhost:11434/v1", + placeholder=_("例如:http://localhost:11434/v1"), id="embedding_endpoint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 密钥:", classes="form-label") + yield Label(_("API 密钥:"), classes="form-label") yield Input( placeholder="sk-123456", password=True, @@ -307,16 +308,16 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("模型名称:", classes="form-label") + yield Label(_("模型名称:"), classes="form-label") yield Input( - placeholder="例如:bge-m3", + placeholder=_("例如:bge-m3"), id="embedding_model", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("验证状态:", classes="form-label") - yield Static("未验证", id="embedding_validation_status", classes="form-input") + yield Label(_("验证状态:"), classes="form-label") + yield Static(_("未验证"), id="embedding_validation_status", classes="form-input") @on(Button.Pressed, "#deploy") async def on_deploy_button_pressed(self) -> None: @@ -326,7 +327,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): is_valid, errors = self.config.validate() if not is_valid: await self.app.push_screen( - ErrorMessageScreen("配置验证失败", errors), + ErrorMessageScreen(_("配置验证失败"), errors), ) return @@ -365,11 +366,11 @@ class DeploymentConfigScreen(ModalScreen[bool]): hint_widget = self.query_one("#embedding_mode_hint", Static) if is_light_mode: hint_widget.update( - "[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/dim]", + _("[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/dim]"), ) else: hint_widget.update( - "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]", + _("[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]"), ) except (AttributeError, ValueError): # 如果控件不存在,忽略错误 @@ -514,24 +515,21 @@ class DeploymentConfigScreen(ModalScreen[bool]): supports_function_call = info.get("supports_function_call", False) if supports_function_call: self.llm_validation_status = ValidationStatus.VALID - status_widget.update(f"[green]✓ {message}[/green]") - self.notify("LLM 验证成功,支持工具调用功能", severity="information") + status_widget.update(_("[green]✓ {message}[/green]").format(message=message)) else: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update("[red]✗ 不支持工具调用[/red]") + status_widget.update(_("[red]✗ 不支持工具调用[/red]")) self.notify( - "LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模型。", + _("LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模型。"), severity="error", ) else: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ {message}[/red]") - self.notify(f"LLM 验证失败: {message}", severity="error") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) except (OSError, ValueError, TypeError) as e: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") - self.notify(f"LLM 验证过程中出现异常: {e}", severity="error") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) # 更新部署按钮状态 self._update_deploy_button_state() @@ -554,19 +552,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): # 更新验证状态 if is_valid: self.embedding_validation_status = ValidationStatus.VALID - status_widget.update(f"[green]✓ {message}[/green]") - # 显示维度信息 dimension = info.get("dimension", "未知") - self.notify(f"Embedding 验证成功,向量维度: {dimension}", severity="information") + status_widget.update(_("[green]✓ {message} (维度: {dimension})[/green]").format( + message=message, + dimension=dimension, + )) else: self.embedding_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ {message}[/red]") - self.notify(f"Embedding 验证失败: {message}", severity="error") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) except (OSError, ValueError, TypeError) as e: self.embedding_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") - self.notify(f"Embedding 验证过程中出现异常: {e}", severity="error") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) # 更新部署按钮状态 self._update_deploy_button_state() @@ -710,17 +707,17 @@ class DeploymentProgressScreen(ModalScreen[bool]): yield OIHeader() with Vertical(classes="progress-section"): - yield Static("部署进度:", id="progress_label") - yield Static("准备开始部署...", id="step_label") + yield Static(_("部署进度:"), id="progress_label") + yield Static(_("准备开始部署..."), id="step_label") with Container(classes="log-section"): yield RichLog(id="deployment_log", highlight=True, markup=True) with Horizontal(classes="button-section"): - yield Button("完成", id="finish", variant="success", disabled=True) - yield Button("重试", id="retry", variant="warning", disabled=True) - yield Button("重新配置", id="reconfigure", variant="primary", disabled=True) - yield Button("取消部署", id="cancel", variant="error") + yield Button(_("完成"), id="finish", variant="success", disabled=True) + yield Button(_("重试"), id="retry", variant="warning", disabled=True) + yield Button(_("重新配置"), id="reconfigure", variant="primary", disabled=True) + yield Button(_("取消部署"), id="cancel", variant="error") async def on_mount(self) -> None: """界面挂载时开始部署""" @@ -755,8 +752,8 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.deployment_cancelled = True # 更新界面 - self.query_one("#step_label", Static).update("部署已取消") - self.query_one("#deployment_log", RichLog).write("部署已被用户取消") + self.query_one("#step_label", Static).update(_("部署已取消")) + self.query_one("#deployment_log", RichLog).write(_("部署已被用户取消")) # 等待任务真正结束 with contextlib.suppress(asyncio.CancelledError): @@ -819,8 +816,8 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.set_interval(0.1, self._check_deployment_status) except (OSError, RuntimeError) as e: - self.query_one("#step_label", Static).update("部署启动失败") - self.query_one("#deployment_log", RichLog).write(f"部署启动失败: {e}") + self.query_one("#step_label", Static).update(_("部署启动失败")) + self.query_one("#deployment_log", RichLog).write(_("部署启动失败: {error}").format(error=e)) self._update_buttons_after_failure() def _check_deployment_status(self) -> None: @@ -836,23 +833,23 @@ class DeploymentProgressScreen(ModalScreen[bool]): except asyncio.CancelledError: if not self.deployment_cancelled: self.deployment_cancelled = True - self.query_one("#step_label", Static).update("部署已取消") - self.query_one("#deployment_log", RichLog).write("部署被取消") + self.query_one("#step_label", Static).update(_("部署已取消")) + self.query_one("#deployment_log", RichLog).write(_("部署被取消")) self._update_buttons_after_failure() except (OSError, RuntimeError, ValueError) as e: - self.query_one("#step_label", Static).update("部署异常") - self.query_one("#deployment_log", RichLog).write(f"部署异常: {e}") + self.query_one("#step_label", Static).update(_("部署异常")) + self.query_one("#deployment_log", RichLog).write(_("部署异常: {error}").format(error=e)) self._update_buttons_after_failure() async def _execute_deployment(self) -> None: """执行部署过程""" try: # 步骤1:检查并安装依赖 - self.query_one("#step_label", Static).update("正在检查部署环境...") + self.query_one("#step_label", Static).update(_("正在检查部署环境...")) success, errors = await self.service.check_and_install_dependencies(self._on_progress_update) if not success: - self.query_one("#step_label", Static).update("环境检查失败") + self.query_one("#step_label", Static).update(_("环境检查失败")) for error in errors: self.query_one("#deployment_log", RichLog).write(f"[red]✗ {error}[/red]") self.deployment_errors.append(error) @@ -860,35 +857,34 @@ class DeploymentProgressScreen(ModalScreen[bool]): return # 步骤2:执行部署 - self.query_one("#step_label", Static).update("正在执行部署...") + self.query_one("#step_label", Static).update(_("正在执行部署...")) success = await self.service.deploy(self.config, self._on_progress_update) # 更新界面状态 if success: self.deployment_success = True - self.query_one("#step_label", Static).update("部署完成!") + self.query_one("#step_label", Static).update(_("部署完成!")) self.query_one("#deployment_log", RichLog).write( - "[bold green]部署成功完成![/bold green]", + _("[bold green]部署成功完成![/bold green]"), ) self._update_buttons_after_success() - self.notify("部署成功完成!", severity="information") + self.notify(_("部署成功完成!"), severity="information") else: - self.query_one("#step_label", Static).update("部署失败") + self.query_one("#step_label", Static).update(_("部署失败")) self.query_one("#deployment_log", RichLog).write( - "[bold red]部署失败,请查看上面的错误信息[/bold red]", + _("[bold red]部署失败,请查看上面的错误信息[/bold red]"), ) - self.deployment_errors.append("部署执行失败") + self.deployment_errors.append(_("部署执行失败")) self._update_buttons_after_failure() - self.notify("部署失败,可以重试或重新配置参数", severity="error") + self.notify(_("部署失败,可以重试或重新配置参数"), severity="error") except OSError as e: - error_msg = f"部署过程中发生异常: {e}" - self.query_one("#step_label", Static).update("部署异常") + error_msg = _("部署过程中发生异常: {error}").format(error=e) + self.query_one("#step_label", Static).update(_("部署异常")) self.query_one("#deployment_log", RichLog).write(f"[bold red]{error_msg}[/bold red]") self.deployment_errors.append(error_msg) self._update_buttons_after_failure() - self.notify("部署异常,可以重试或重新配置参数", severity="error") def _on_progress_update(self, state: DeploymentState) -> None: """处理进度更新""" @@ -902,7 +898,11 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.deployment_progress_value = progress # 更新步骤标签 - step_text = f"步骤 {state.current_step}/{state.total_steps}: {state.current_step_name}" + step_text = _("步骤 {current}/{total}: {name}").format( + current=state.current_step, + total=state.total_steps, + name=state.current_step_name, + ) self.query_one("#step_label", Static).update(step_text) # 添加最新的日志条目 @@ -971,13 +971,13 @@ class ErrorMessageScreen(ModalScreen[None]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="error-container"): - yield Static(self.title or "错误", classes="error-title") + yield Static(self.title or _("错误"), classes="error-title") with Vertical(classes="error-list"): for message in self.messages: yield Static(f"• {message}") - yield Button("确定", id="ok", variant="primary") + yield Button(_("确定"), id="ok", variant="primary") @on(Button.Pressed, "#ok") def on_ok_button_pressed(self) -> None: diff --git a/src/tool/oi_backend_init.py b/src/tool/oi_backend_init.py index be4c055..768da72 100644 --- a/src/tool/oi_backend_init.py +++ b/src/tool/oi_backend_init.py @@ -8,6 +8,7 @@ from textual.app import App from app.deployment import InitializationModeScreen from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger @@ -35,7 +36,7 @@ def backend_init() -> None: """部署 TUI 应用""" CSS_PATH = css_path - TITLE = "openEuler Intelligence 部署助手" + TITLE = _("openEuler Intelligence 部署助手") def on_mount(self) -> None: """启动时先显示初始化模式选择界面""" -- Gitee