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
321 lines
15 KiB
Python
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",
|
|
)
|