Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

321 lines
15 KiB
Python

"""Branded banner + localized labels for ``deeptutor start`` / ``deeptutor init``.
Both commands read the user's language preference from
``data/user/settings/interface.json`` (default ``en``) so their startup
output matches the UI language the user has chosen.
"""
from __future__ import annotations
from rich.align import Align
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from deeptutor.__version__ import __version__
_ASCII_LOGO = r""" ____ _____ _
| _ \ ___ ___ _ __ |_ _| _| |_ ___ _ __
| | | |/ _ \/ _ \ '_ \ | || | | | __/ _ \| '__|
| |_| | __/ __/ |_) | | || |_| | || (_) | |
|____/ \___|\___| .__/ |_| \__,_|\__\___/|_|
|_|"""
LABELS: dict[str, dict[str, str]] = {
"en": {
"tagline": "Agent-Native Personalized Tutoring",
"lab": "Data Intelligence Lab @ HKU",
# init
"init.mode": "Workspace initializer",
"init.workspace": "Workspace",
"init.note_settings_dir": "Settings will be written under data/user/settings.",
"init.cancelled": "Setup cancelled. Nothing was saved.",
"init.step_ports": "Step {n}/{total} · Ports",
"init.step_llm": "Step {n}/{total} · LLM provider",
"init.step_embedding": "Step {n}/{total} · Embedding (RAG / Knowledge Base)",
"init.step_search": "Step {n}/{total} · Web search",
"init.step_review": "Step {n}/{total} · Review & save",
"init.backend_port": "Backend port",
"init.frontend_port": "Frontend port",
"init.llm_section": "LLM provider",
"init.pick_provider": "Pick an LLM provider",
"init.pick_embedding_provider": "Pick an embedding provider",
"init.show_all": "Show all providers",
"init.custom_provider": "Custom / Other",
"init.skip_step": "Skip / configure later",
"init.back": "Back",
"init.skipped": "Skipped — you can configure this later in Web Settings.",
"init.binding": "Binding",
"init.base_url": "Base URL",
"init.api_key": "API key",
"init.api_key_env_detected": "Detected {env_var}={masked} in your environment. Use it?",
"init.api_key_prompt": "API key (input hidden, Enter to skip)",
"init.api_key_reuse_llm": "Reuse the LLM API key {masked}?",
"init.edit_base_url": "Edit Base URL?",
"init.new_base_url": "New Base URL",
"init.model": "Model",
"init.fetch_models": "Fetching available models from {url} ...",
"init.fetch_models_ok": "Found {count} model(s).",
"init.fetch_models_fail": "Could not list models ({error}). Using fallback list.",
"init.pick_model": "Pick a model — or type {marker} to enter your own",
"init.custom_model": "Custom model name",
"init.embedding_section": "Embedding provider",
"init.embedding_endpoint": "Embedding endpoint URL",
"init.embedding_api_key": "Embedding API key",
"init.embedding_model": "Embedding model",
"init.embedding_dimension": "Embedding dimension (blank for auto)",
"init.search_section": "Web search provider",
"init.pick_search_provider": "Pick a web-search provider",
"init.search_api_key_prompt": "API key (input hidden, Enter to skip)",
"init.search_base_url_prompt": "Base URL",
"init.search_no_key_note": "{label} does not need an API key.",
"init.search_disabled_note": "Web search will be disabled. Agents will skip search tools.",
"init.review_search": "Search",
"init.review_search_disabled": "disabled",
"init.probe_offer": "Test connection now?",
"init.probe_running": "Testing {what} ...",
"init.probe_ok": "{what} OK · {ms}ms",
"init.probe_fail": "{what} failed: {error}",
"init.probe_retry": "Re-enter API key and retry?",
"init.review_title": "Review",
"init.review_llm": "LLM",
"init.review_embedding": "Embedding",
"init.review_ports": "Ports",
"init.review_ports_value": "backend {backend}, frontend {frontend}",
"init.confirm_save": "Save these settings?",
"init.saved": "Settings saved. You can edit them later in the Web Settings page or data/user/settings/.",
"init.next_step": "Run `deeptutor start` to launch DeepTutor.",
"init.choice": "Choice",
"init.choice_invalid": "Invalid choice. Try again.",
# start (launcher)
"start.mode": "Launching backend + frontend",
"start.backend": "Backend",
"start.browser_api": "Browser API",
"start.frontend": "Frontend",
"start.workspace": "Workspace",
"start.frontend_runtime": "Frontend runtime",
"start.press_ctrl_c": "Press Ctrl+C to stop.",
"start.starting_backend": "Starting backend ...",
"start.starting_frontend": "Starting frontend ...",
"start.reusing_frontend": "Reusing existing frontend at {url} (PID {pid}).",
"start.restarting_frontend": (
"Existing frontend at {url} is not responding; restarting it (PID {pid})."
),
"start.frontend_restart_failed": (
"Existing frontend at {url} is not responding and could not be stopped automatically. "
"Stop PID {pid} and run `deeptutor start` again."
),
"start.waiting_for": "Waiting for {name} at {url} ...",
"start.ready": "{name} is ready.",
"start.open_in_browser": "Open {url} in your browser.",
"start.received_signal": "Received {signal}; shutting down ...",
"start.stopping": "Stopping {name} (PID {pid})",
"start.exited": "{name} exited with code {code}",
"start.not_ready": "{name} did not become ready within {timeout}s",
"start.port_in_use": (
"DeepTutor cannot start because port(s) already in use: {ports}. "
"Stop the existing process or change data/user/settings/system.json."
),
"start.port_conflict_title": "Port conflict detected:",
"start.port_conflict_line": " {role} port {port} is in use by:",
"start.port_conflict_proc": " PID {pid} · {command}",
"start.port_conflict_unknown_proc": " (process info unavailable)",
"start.port_option_change": "Change ports (saved to data/user/settings/system.json)",
"start.port_option_kill": "Stop the occupying process(es) and continue",
"start.port_invalid": "Invalid port: {value}. Enter a free port between 1 and 65535.",
"start.port_saved": "Ports saved to {path}.",
"start.port_killing": "Stopping PID {pid} ({command}) ...",
"start.port_kill_failed": "Could not free port {port} (PID {pid}).",
"start.port_freed": "Port {port} released.",
},
"zh": {
"tagline": "智能体原生的个性化辅导",
"lab": "香港大学数据智能实验室",
# init
"init.mode": "工作目录初始化",
"init.workspace": "工作目录",
"init.note_settings_dir": "配置文件将写入 data/user/settings 目录。",
"init.cancelled": "已取消,未保存任何更改。",
"init.step_ports": "第 {n}/{total} 步 · 端口",
"init.step_llm": "第 {n}/{total} 步 · 大模型服务",
"init.step_embedding": "第 {n}/{total} 步 · 向量模型 (知识库 / RAG)",
"init.step_search": "第 {n}/{total} 步 · 联网搜索",
"init.step_review": "第 {n}/{total} 步 · 确认并保存",
"init.backend_port": "后端端口",
"init.frontend_port": "前端端口",
"init.llm_section": "大模型服务",
"init.pick_provider": "请选择大模型服务",
"init.pick_embedding_provider": "请选择向量模型服务",
"init.show_all": "查看全部服务商",
"init.custom_provider": "其他 / 自定义",
"init.skip_step": "跳过 / 稍后配置",
"init.back": "返回上一步",
"init.skipped": "已跳过 —— 后续可在 Web 设置页中配置。",
"init.binding": "服务类型",
"init.base_url": "Base URL",
"init.api_key": "API Key",
"init.api_key_env_detected": "已检测到环境变量 {env_var}={masked},是否使用?",
"init.api_key_prompt": "API Key (输入不显示,直接回车跳过)",
"init.api_key_reuse_llm": "复用大模型的 API Key ({masked})?",
"init.edit_base_url": "需要修改 Base URL 吗?",
"init.new_base_url": "新的 Base URL",
"init.model": "模型",
"init.fetch_models": "正在从 {url} 拉取可用模型列表 ...",
"init.fetch_models_ok": "找到 {count} 个模型。",
"init.fetch_models_fail": "无法获取模型列表 ({error}),将使用本地推荐列表。",
"init.pick_model": "请选择模型 —— 或输入 {marker} 手动填写",
"init.custom_model": "自定义模型名称",
"init.embedding_section": "向量模型服务",
"init.embedding_endpoint": "向量服务地址",
"init.embedding_api_key": "向量服务 API Key",
"init.embedding_model": "向量模型",
"init.embedding_dimension": "向量维度 (留空自动检测)",
"init.search_section": "联网搜索服务",
"init.pick_search_provider": "请选择联网搜索服务",
"init.search_api_key_prompt": "API Key (输入不显示,直接回车跳过)",
"init.search_base_url_prompt": "Base URL",
"init.search_no_key_note": "{label} 无需 API Key。",
"init.search_disabled_note": "联网搜索将被禁用。Agent 将跳过搜索工具。",
"init.review_search": "搜索",
"init.review_search_disabled": "已禁用",
"init.probe_offer": "立即测试连接?",
"init.probe_running": "正在测试 {what} ...",
"init.probe_ok": "{what} 连接成功 · {ms}ms",
"init.probe_fail": "{what} 连接失败: {error}",
"init.probe_retry": "重新输入 API Key 并重试?",
"init.review_title": "配置确认",
"init.review_llm": "大模型",
"init.review_embedding": "向量",
"init.review_ports": "端口",
"init.review_ports_value": "后端 {backend},前端 {frontend}",
"init.confirm_save": "确认保存以上配置?",
"init.saved": "配置已保存。后续可在 Web 设置页或 data/user/settings/ 中修改。",
"init.next_step": "运行 `deeptutor start` 启动 DeepTutor。",
"init.choice": "请选择",
"init.choice_invalid": "无效选项,请重新输入。",
# start (launcher)
"start.mode": "启动后端 + 前端",
"start.backend": "后端",
"start.browser_api": "前端 API",
"start.frontend": "前端",
"start.workspace": "工作目录",
"start.frontend_runtime": "前端运行模式",
"start.press_ctrl_c": "按 Ctrl+C 停止。",
"start.starting_backend": "正在启动后端服务 ...",
"start.starting_frontend": "正在启动前端服务 ...",
"start.reusing_frontend": "复用已运行的前端 {url} (PID {pid})。",
"start.restarting_frontend": "已运行的前端 {url} 无响应,正在重启 (PID {pid})。",
"start.frontend_restart_failed": (
"已运行的前端 {url} 无响应,且无法自动停止。"
"请先停止 PID {pid},然后重新运行 `deeptutor start`。"
),
"start.waiting_for": "正在等待 {name} ({url}) ...",
"start.ready": "{name} 已就绪。",
"start.open_in_browser": "请在浏览器中打开 {url}。",
"start.received_signal": "收到 {signal} 信号,正在关闭 ...",
"start.stopping": "正在停止 {name} (PID {pid})",
"start.exited": "{name} 已退出 (退出码 {code})",
"start.not_ready": "{name}{timeout} 秒内未就绪",
"start.port_in_use": (
"无法启动 DeepTutor,端口已被占用: {ports}。"
"请先停止占用进程,或修改 data/user/settings/system.json 中的端口设置。"
),
"start.port_conflict_title": "检测到端口被占用:",
"start.port_conflict_line": " {role}端口 {port} 被以下进程占用:",
"start.port_conflict_proc": " PID {pid} · {command}",
"start.port_conflict_unknown_proc": " (无法获取进程信息)",
"start.port_option_change": "更改端口设置 (写入 data/user/settings/system.json)",
"start.port_option_kill": "停止占用进程并继续启动",
"start.port_invalid": "无效端口: {value}。请输入 1-65535 之间且未被占用的端口。",
"start.port_saved": "端口设置已保存到 {path}。",
"start.port_killing": "正在停止 PID {pid} ({command}) ...",
"start.port_kill_failed": "无法释放端口 {port} (PID {pid})。",
"start.port_freed": "端口 {port} 已释放。",
},
}
def _pick_language(language: str | None) -> str:
if not language:
return "en"
code = str(language).lower().strip()
if code in {"zh", "zh-cn", "zh-hans", "chinese", "cn"}:
return "zh"
return "en"
def resolve_language(default: str = "en") -> str:
"""Read the saved UI language, falling back to ``default``.
Safe to call before the runtime is fully initialized; any failure
silently falls back to the default.
"""
try:
from deeptutor.services.settings.interface_settings import get_ui_language
return _pick_language(get_ui_language(default))
except Exception:
return _pick_language(default)
def labels_for(language: str | None) -> dict[str, str]:
return LABELS[_pick_language(language)]
def render_banner(language: str | None, *, mode_key: str | None = None) -> Panel:
"""Build the branded banner panel.
Parameters
----------
language:
Language code (``"en"``/``"zh"``). Unknown values fall back to English.
mode_key:
Optional key into ``LABELS[lang]`` to display under the tagline
(e.g. ``"start.mode"``, ``"init.mode"``).
"""
lang = _pick_language(language)
strings = LABELS[lang]
logo = Text(_ASCII_LOGO, style="bold bright_cyan")
tagline_line = f"{strings['tagline']} · v{__version__}"
body = Text()
body.append(logo)
body.append("\n\n")
body.append(tagline_line, style="bold white")
body.append("\n")
body.append(strings["lab"], style="dim")
if mode_key and mode_key in strings:
body.append("\n")
body.append(strings[mode_key], style="italic bright_magenta")
return Panel(
Align.left(body),
title="[bold bright_cyan]DeepTutor[/]",
border_style="bright_cyan",
padding=(1, 2),
)
def print_banner(
console: Console | None = None,
*,
language: str | None = None,
mode_key: str | None = None,
) -> None:
"""Print the branded banner to ``console`` (creates one if omitted)."""
target = console or Console()
target.print(render_banner(language, mode_key=mode_key))
__all__: tuple[str, ...] = (
"LABELS",
"labels_for",
"print_banner",
"render_banner",
"resolve_language",
)