From a924e69232d7f3ff894072f2632769e83a14d39a Mon Sep 17 00:00:00 2001 From: 1247691206 <1247691206@qq.com> Date: Wed, 20 May 2026 17:19:32 +0800 Subject: [PATCH 1/5] feat: add dynamic_router compat gateway - Add single-port compat gateway on 3000 with /health - Proxy UI/API and WebSocket (/api/v1/ws) behind /u/ prefix - Persist DeepTutor data under /workspace/deeptutor via DEEPTUTOR_WORKSPACE_ROOT Co-authored-by: Cursor --- Dockerfile | 37 ++++++- deeptutor/services/path_service.py | 13 +++ deeptutor/services/setup/init.py | 8 +- deeptutor_compat/__init__.py | 2 + deeptutor_compat/gateway.py | 171 +++++++++++++++++++++++++++++ web/lib/api.ts | 14 +++ 6 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 deeptutor_compat/__init__.py create mode 100644 deeptutor_compat/gateway.py diff --git a/Dockerfile b/Dockerfile index 6526bbf7c4..6f65495667 100644 --- a/Dockerfile +++ b/Dockerfile @@ -157,6 +157,7 @@ COPY --from=frontend-builder /app/web/public/ ./web/public/ # Copy application source code COPY deeptutor/ ./deeptutor/ +COPY deeptutor_compat/ ./deeptutor_compat/ COPY deeptutor_cli/ ./deeptutor_cli/ COPY scripts/ ./scripts/ COPY pyproject.toml ./ @@ -180,7 +181,7 @@ RUN mkdir -p \ data/user/logs \ data/knowledge_bases -# Create supervisord configuration for running both services +# Create supervisord configuration for running backend + frontend + compat gateway # Log output goes to stdout/stderr so docker logs can capture them RUN mkdir -p /etc/supervisor/conf.d @@ -213,6 +214,18 @@ stdout_logfile_maxbytes=0 stderr_logfile=/dev/fd/2 stderr_logfile_maxbytes=0 environment=NODE_ENV="production" + +[program:compat-gateway] +command=/bin/bash /app/start-compat-gateway.sh +directory=/app +autostart=true +autorestart=true +startsecs=2 +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/fd/2 +stderr_logfile_maxbytes=0 +environment=PYTHONPATH="/app",PYTHONUNBUFFERED="1" EOF RUN sed -i 's/\r$//' /etc/supervisor/conf.d/deeptutor.conf @@ -279,6 +292,20 @@ EOF RUN sed -i 's/\r$//' /app/start-frontend.sh && chmod +x /app/start-frontend.sh +# Create compat gateway startup script (single-port entrypoint for dynamic_router) +RUN cat > /app/start-compat-gateway.sh <<'EOF' +#!/bin/bash +set -e + +GATEWAY_PORT=${GATEWAY_PORT:-3000} + +echo "[Compat] 🚪 Starting compat gateway on port ${GATEWAY_PORT}..." + +exec python -m uvicorn deeptutor_compat.gateway:app --host 0.0.0.0 --port ${GATEWAY_PORT} +EOF + +RUN sed -i 's/\r$//' /app/start-compat-gateway.sh && chmod +x /app/start-compat-gateway.sh + # Create entrypoint script RUN cat > /app/entrypoint.sh <<'EOF' #!/bin/bash @@ -291,9 +318,13 @@ echo "============================================" # Set default ports if not provided export BACKEND_PORT=${BACKEND_PORT:-8001} export FRONTEND_PORT=${FRONTEND_PORT:-3782} +export GATEWAY_PORT=${GATEWAY_PORT:-3000} +export DEEPTUTOR_WORKSPACE_ROOT=${DEEPTUTOR_WORKSPACE_ROOT:-/workspace/deeptutor} echo "📌 Backend Port: ${BACKEND_PORT}" echo "📌 Frontend Port: ${FRONTEND_PORT}" +echo "📌 Gateway Port: ${GATEWAY_PORT}" +echo "📌 DeepTutor Data Root: ${DEEPTUTOR_WORKSPACE_ROOT}" # Check for required environment variables if [ -z "$LLM_API_KEY" ]; then @@ -329,11 +360,11 @@ EOF RUN sed -i 's/\r$//' /app/entrypoint.sh && chmod +x /app/entrypoint.sh # Expose ports -EXPOSE 8001 3782 +EXPOSE 3000 8001 3782 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:${BACKEND_PORT:-8001}/ || exit 1 + CMD curl -f http://localhost:${GATEWAY_PORT:-3000}/health || exit 1 # Set entrypoint ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/deeptutor/services/path_service.py b/deeptutor/services/path_service.py index 30ea773031..4813ff98d9 100644 --- a/deeptutor/services/path_service.py +++ b/deeptutor/services/path_service.py @@ -22,6 +22,7 @@ └── _detached_code_execution/ """ +import os from pathlib import Path from typing import Literal, cast @@ -78,6 +79,18 @@ class PathService: def __init__(self, workspace_root: Path | None = None): self._project_root = Path(__file__).resolve().parent.parent.parent self._uses_default_workspace_root = workspace_root is None + # Allow deployments (e.g. dynamic_router user containers) to relocate + # DeepTutor runtime data under a mounted workspace directory. + # + # Example: + # DEEPTUTOR_WORKSPACE_ROOT=/workspace/deeptutor + # + # This keeps DeepTutor fully self-contained under /workspace/deeptutor + # without polluting other paths mounted at /workspace. + env_value = str(os.getenv("DEEPTUTOR_WORKSPACE_ROOT", "")).strip() + if env_value: + workspace_root = Path(env_value) + self._workspace_root = (workspace_root or self._project_root / "data").resolve() self._user_data_dir = (self._workspace_root / "user").resolve() diff --git a/deeptutor/services/setup/init.py b/deeptutor/services/setup/init.py index 86b01a6c66..3f5b2beb8d 100644 --- a/deeptutor/services/setup/init.py +++ b/deeptutor/services/setup/init.py @@ -46,7 +46,13 @@ }, "tools": { "run_code": { - "allowed_roots": ["./data/user"], + # Loose mode for shared deployments: allow the whole DeepTutor data root + # under the mounted workspace (dynamic_router user containers mount /workspace). + # This keeps DeepTutor self-contained under /workspace/deeptutor without + # polluting /workspace/{agents,skills,mcps,envs}. + # + # Note: these defaults are only written when settings files are missing. + "allowed_roots": ["/workspace/deeptutor", "./data/user", "./data"], }, "web_search": { "enabled": True, diff --git a/deeptutor_compat/__init__.py b/deeptutor_compat/__init__.py new file mode 100644 index 0000000000..fe16459e07 --- /dev/null +++ b/deeptutor_compat/__init__.py @@ -0,0 +1,2 @@ +__all__ = [] + diff --git a/deeptutor_compat/gateway.py b/deeptutor_compat/gateway.py new file mode 100644 index 0000000000..eec0e81b83 --- /dev/null +++ b/deeptutor_compat/gateway.py @@ -0,0 +1,171 @@ +import os +from typing import Iterable + +import httpx +from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect +from fastapi.responses import StreamingResponse + + +def _int(name: str, default: int) -> int: + try: + return int(os.getenv(name, str(default))) + except (TypeError, ValueError): + return default + + +BACKEND_PORT = _int("BACKEND_PORT", 8001) +FRONTEND_PORT = _int("FRONTEND_PORT", 3782) + + +def create_gateway_app() -> FastAPI: + """ + A thin compat gateway that makes DeepTutor look like the agent_gateway + contract expected by dynamic_router: + - listens on a single port (dynamic_router uses CONTAINER_PORT, default 3000) + - GET /health for readiness + - proxies /api/* and /docs, /openapi.json to backend + - proxies everything else to the Next.js frontend + - proxies WebSocket /api/v1/ws to backend + """ + + gateway_app = FastAPI(title="DeepTutor Compat Gateway", docs_url=None, redoc_url=None) + + @gateway_app.get("/health") + async def health(): + return {"status": "ok"} + + def _backend_url(path: str, query: str | None) -> str: + url = f"http://127.0.0.1:{BACKEND_PORT}{path}" + if query: + url += f"?{query}" + return url + + def _frontend_url(path: str, query: str | None) -> str: + url = f"http://127.0.0.1:{FRONTEND_PORT}{path}" + if query: + url += f"?{query}" + return url + + def _copy_headers(src: Iterable[tuple[str, str]]) -> dict[str, str]: + # Keep cookies/auth, but strip hop-by-hop headers. + skip = { + "host", + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailers", + "transfer-encoding", + "upgrade", + } + out: dict[str, str] = {} + for k, v in src: + if k.lower() not in skip: + out[k] = v + return out + + @gateway_app.websocket("/api/v1/ws") + async def ws_proxy(ws: WebSocket): + await ws.accept() + upstream = f"ws://127.0.0.1:{BACKEND_PORT}/api/v1/ws" + + # Prefer websockets if available (uvicorn[standard] installs it). + import websockets # type: ignore + + try: + async with websockets.connect( + upstream, + extra_headers=_copy_headers(ws.headers.raw), + max_size=None, + ) as upstream_ws: + + async def _client_to_upstream(): + try: + while True: + msg = await ws.receive() + if "text" in msg and msg["text"] is not None: + await upstream_ws.send(msg["text"]) + elif "bytes" in msg and msg["bytes"] is not None: + await upstream_ws.send(msg["bytes"]) + else: + break + except WebSocketDisconnect: + pass + except RuntimeError: + pass + + async def _upstream_to_client(): + try: + async for msg in upstream_ws: + if isinstance(msg, bytes): + await ws.send_bytes(msg) + else: + await ws.send_text(msg) + except RuntimeError: + pass + + import asyncio + + await asyncio.gather(_client_to_upstream(), _upstream_to_client()) + finally: + try: + await ws.close() + except RuntimeError: + pass + + @gateway_app.api_route( + "/{path:path}", + methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"], + ) + async def proxy_all(request: Request, path: str): + # Route selection: + # - backend: /api/*, /docs, /openapi.json (and any future backend root assets) + # - frontend: everything else + full_path = "/" + path + to_backend = ( + full_path.startswith("/api/") + or full_path == "/api" + or full_path.startswith("/docs") + or full_path == "/openapi.json" + ) + + target = ( + _backend_url(full_path, request.url.query) if to_backend else _frontend_url(full_path, request.url.query) + ) + + headers = _copy_headers(request.headers.items()) + body = await request.body() + + timeout = httpx.Timeout(300.0, connect=10.0) + async with httpx.AsyncClient(timeout=timeout) as client: + upstream = await client.send( + client.build_request( + method=request.method, + url=target, + headers=headers, + content=body, + ), + stream=True, + ) + + resp_headers = dict(upstream.headers) + for h in ("transfer-encoding", "connection", "keep-alive"): + resp_headers.pop(h, None) + + async def stream_body(): + async for chunk in upstream.aiter_bytes(): + yield chunk + await upstream.aclose() + + return StreamingResponse( + content=stream_body(), + status_code=upstream.status_code, + headers=resp_headers, + ) + + return gateway_app + + +app = create_gateway_app() + diff --git a/web/lib/api.ts b/web/lib/api.ts index b001cb3d4a..377fb0e832 100644 --- a/web/lib/api.ts +++ b/web/lib/api.ts @@ -52,6 +52,20 @@ function isLoopbackHost(host: string): boolean { export function resolveBase(): string { const base = API_BASE_URL; if (typeof window === "undefined") return base; + + // When DeepTutor is hosted behind dynamic_router, the browser URL looks like: + // http(s)://:/u//... + // In that case, the API must be routed through the same /u/ prefix + // so dynamic_router can inject x-finclaw-user and forward to the right container. + // + // We derive the effective base from window.location instead of relying on + // NEXT_PUBLIC_API_BASE (which cannot encode per-user prefixes at build time). + const m = window.location.pathname.match(/^\/u\/([^/]+)(?:\/|$)/); + if (m) { + const userPrefix = `/u/${m[1]}`; + return `${window.location.origin}${userPrefix}`.replace(/\/+$/, ""); + } + try { const url = new URL(base); const clientHost = window.location.hostname; From ff91bfc5aba456ddd23ad54bd48e7f221ae8fe8f Mon Sep 17 00:00:00 2001 From: 1247691206 <1247691206@qq.com> Date: Sun, 7 Jun 2026 12:11:29 +0800 Subject: [PATCH 2/5] Support Levels practice notebook sessions in SQLite store. Co-authored-by: Cursor --- deeptutor/api/routers/question_notebook.py | 22 +++++++++++++++++++++- deeptutor/services/session/sqlite_store.py | 6 ++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/deeptutor/api/routers/question_notebook.py b/deeptutor/api/routers/question_notebook.py index 151a335454..1c960b9a67 100644 --- a/deeptutor/api/routers/question_notebook.py +++ b/deeptutor/api/routers/question_notebook.py @@ -70,6 +70,8 @@ class CategoryAddRequest(BaseModel): class UpsertEntryRequest(BaseModel): session_id: str + """When the session does not exist yet (e.g. OxCa Levels practice), create it.""" + session_title: str | None = None question_id: str question: str question_type: str = "" @@ -81,14 +83,32 @@ class UpsertEntryRequest(BaseModel): is_correct: bool = False +class EnsureSessionRequest(BaseModel): + session_id: str + title: str = Field(default="Practice notebook", min_length=1, max_length=100) + + # ── Entry endpoints ────────────────────────────────────────────── +@router.post("/sessions/ensure") +async def ensure_notebook_session(payload: EnsureSessionRequest): + """Create an empty chat session used to group notebook entries (Levels practice).""" + store = get_sqlite_session_store() + if await store.get_session(payload.session_id) is None: + await store.create_session(title=payload.title[:100], session_id=payload.session_id) + return {"session_id": payload.session_id} + + @router.post("/entries/upsert") async def upsert_single_entry(payload: UpsertEntryRequest): store = get_sqlite_session_store() + if await store.get_session(payload.session_id) is None: + title = (payload.session_title or "Practice notebook").strip() or "Practice notebook" + await store.create_session(title=title[:100], session_id=payload.session_id) + entry_payload = payload.model_dump(exclude={"session_title"}) try: - await store.upsert_notebook_entries(payload.session_id, [payload.model_dump()]) + await store.upsert_notebook_entries(payload.session_id, [entry_payload]) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) entry = await store.find_notebook_entry(payload.session_id, payload.question_id) diff --git a/deeptutor/services/session/sqlite_store.py b/deeptutor/services/session/sqlite_store.py index c18682a9d8..a2c60324bb 100644 --- a/deeptutor/services/session/sqlite_store.py +++ b/deeptutor/services/session/sqlite_store.py @@ -863,6 +863,12 @@ def _upsert_notebook_entries_sync(self, session_id: str, items: list[dict[str, A created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', ?, ?) ON CONFLICT(session_id, question_id) DO UPDATE SET + question = excluded.question, + question_type = excluded.question_type, + options_json = excluded.options_json, + correct_answer = excluded.correct_answer, + explanation = excluded.explanation, + difficulty = excluded.difficulty, user_answer = excluded.user_answer, is_correct = excluded.is_correct, updated_at = excluded.updated_at From 677012e27f81719b8606c327623488038cd03e10 Mon Sep 17 00:00:00 2001 From: 1247691206 <1247691206@qq.com> Date: Mon, 8 Jun 2026 22:12:20 +0800 Subject: [PATCH 3/5] Use tutor history context for inline quiz ideation. Prefer context.history_context over conversation_context_text so Quiz me from a tutor session can ground questions on the prior dialogue. Co-authored-by: Cursor --- deeptutor/agents/question/agents/idea_agent.py | 7 ++++++- deeptutor/agents/question/coordinator.py | 1 + deeptutor/capabilities/deep_question.py | 8 +++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/deeptutor/agents/question/agents/idea_agent.py b/deeptutor/agents/question/agents/idea_agent.py index 3535de639f..e0ebee5e11 100644 --- a/deeptutor/agents/question/agents/idea_agent.py +++ b/deeptutor/agents/question/agents/idea_agent.py @@ -49,13 +49,18 @@ async def process( existing_concentrations: list[str] | None = None, batch_number: int | None = None, attachments: list[Any] | None = None, + history_context: str = "", ) -> dict[str, Any]: """ Build grounded question templates in a single pass. """ batch_size = max(1, min(int(num_ideas or 1), BATCH_SIZE)) trace_id = f"batch-{batch_number}" if batch_number is not None else "ideation" - if self.enable_rag and self.kb_name: + attached_history = str(history_context or "").strip() + if attached_history: + knowledge_context = attached_history + retrievals: list[dict[str, Any]] = [] + elif self.enable_rag and self.kb_name: retrievals = await self._retrieve_context( user_topic, trace_id=trace_id, diff --git a/deeptutor/agents/question/coordinator.py b/deeptutor/agents/question/coordinator.py index 8fb0eb90ec..b4676a6d14 100644 --- a/deeptutor/agents/question/coordinator.py +++ b/deeptutor/agents/question/coordinator.py @@ -158,6 +158,7 @@ async def generate_from_topic( existing_concentrations=existing_concentrations, batch_number=batch_number, attachments=attachments, + history_context=history_context, ) batch_templates = idea_result.get("templates", []) if not isinstance(batch_templates, list): diff --git a/deeptutor/capabilities/deep_question.py b/deeptutor/capabilities/deep_question.py index 739708ba27..90a9727769 100644 --- a/deeptutor/capabilities/deep_question.py +++ b/deeptutor/capabilities/deep_question.py @@ -88,7 +88,13 @@ async def run(self, context: UnifiedContext, stream: StreamBus) -> None: difficulty = str(overrides.get("difficulty", "") or "") question_type = str(overrides.get("question_type", "") or "") preference = str(overrides.get("preference", "") or "") - history_context = str(context.metadata.get("conversation_context_text", "") or "").strip() + # Tutor-inline quiz runs in a fresh session; the tutor transcript arrives + # via history_references → context.history_context, not conversation_context_text. + history_context = str(context.history_context or "").strip() + if not history_context: + history_context = str( + context.metadata.get("conversation_context_text", "") or "" + ).strip() enabled_tools = set( self.manifest.tools_used if context.enabled_tools is None else context.enabled_tools ) From 9a26729ff1459290fea357136132a41138dba76e Mon Sep 17 00:00:00 2001 From: 1247691206 <1247691206@qq.com> Date: Thu, 11 Jun 2026 15:29:41 +0800 Subject: [PATCH 4/5] White-label OxCa branding for student and admin-facing surfaces. Replace DeepTutor identity in chat prompts, answer_now paths, native /u/ UI, and API labels with OxCa guardrails and product branding. Co-authored-by: Cursor --- deeptutor/agents/chat/agentic_pipeline.py | 6 +- deeptutor/agents/chat/chat_agent.py | 8 ++- .../agents/chat/prompts/en/agentic_chat.yaml | 7 +- .../agents/chat/prompts/en/chat_agent.yaml | 2 +- .../agents/chat/prompts/zh/agentic_chat.yaml | 7 +- .../agents/chat/prompts/zh/chat_agent.yaml | 2 +- .../question/prompts/en/answer_now.yaml | 2 +- .../question/prompts/zh/answer_now.yaml | 2 +- .../research/prompts/en/answer_now.yaml | 2 +- .../research/prompts/zh/answer_now.yaml | 2 +- .../agents/solve/prompts/en/answer_now.yaml | 2 +- .../agents/solve/prompts/zh/answer_now.yaml | 2 +- .../visualize/prompts/en/answer_now.yaml | 2 +- .../visualize/prompts/zh/answer_now.yaml | 2 +- deeptutor/api/main.py | 4 +- deeptutor/capabilities/_answer_now.py | 9 +++ deeptutor/capabilities/deep_question.py | 3 +- deeptutor/capabilities/deep_research.py | 3 +- deeptutor/capabilities/deep_solve.py | 3 +- deeptutor/capabilities/visualize.py | 3 +- deeptutor/services/prompt/__init__.py | 2 + deeptutor/services/prompt/oxca_brand.py | 31 +++++++++ deeptutor/services/setup/init.py | 2 +- deeptutor_compat/gateway.py | 2 +- web/app/(admin)/admin/users/page.tsx | 2 +- web/app/(auth)/login/page.tsx | 5 +- web/app/(auth)/register/page.tsx | 5 +- .../(workspace)/co-writer/sampleTemplate.ts | 68 +++++++++---------- web/app/layout.tsx | 5 +- web/components/chat/SkillsPicker.tsx | 2 +- web/components/sidebar/SidebarShell.tsx | 31 ++------- web/components/sidebar/VersionBadge.tsx | 26 +++---- web/lib/product-brand.ts | 5 ++ web/locales/en/app.json | 26 +++---- web/locales/zh/app.json | 26 +++---- 35 files changed, 173 insertions(+), 138 deletions(-) create mode 100644 deeptutor/services/prompt/oxca_brand.py create mode 100644 web/lib/product-brand.ts diff --git a/deeptutor/agents/chat/agentic_pipeline.py b/deeptutor/agents/chat/agentic_pipeline.py index 2a5d3f4a00..a712694bac 100644 --- a/deeptutor/agents/chat/agentic_pipeline.py +++ b/deeptutor/agents/chat/agentic_pipeline.py @@ -35,6 +35,7 @@ ) from deeptutor.services.prompt import get_prompt_manager from deeptutor.services.prompt.language import append_language_directive +from deeptutor.services.prompt.oxca_brand import apply_student_guardrail from deeptutor.tools.builtin import BUILTIN_TOOL_NAMES from deeptutor.utils.json_parser import parse_json_response @@ -1416,7 +1417,10 @@ def _responding_system_prompt(self, enabled_tools: list[str]) -> str: tool_list=tool_list or self._fallback_empty_tool_list(), rag_hint=rag_hint, ) - return append_language_directive(system_prompt, self.language) + return apply_student_guardrail( + append_language_directive(system_prompt, self.language), + self.language, + ) def _acting_user_prompt(self, context: UnifiedContext, thinking_text: str) -> str: return self._t( diff --git a/deeptutor/agents/chat/chat_agent.py b/deeptutor/agents/chat/chat_agent.py index f532c9443e..20d15aec4e 100644 --- a/deeptutor/agents/chat/chat_agent.py +++ b/deeptutor/agents/chat/chat_agent.py @@ -16,6 +16,7 @@ from deeptutor.agents.base_agent import BaseAgent from deeptutor.runtime.registry.tool_registry import get_tool_registry from deeptutor.services.prompt.language import append_language_directive +from deeptutor.services.prompt.oxca_brand import apply_student_guardrail class ChatAgent(BaseAgent): @@ -247,8 +248,11 @@ def build_messages( messages = [] system_parts = [ - append_language_directive( - self.get_prompt("system", "You are a helpful AI assistant."), + apply_student_guardrail( + append_language_directive( + self.get_prompt("system", "You are OxCa's AI learning assistant."), + self.language, + ), self.language, ) ] diff --git a/deeptutor/agents/chat/prompts/en/agentic_chat.yaml b/deeptutor/agents/chat/prompts/en/agentic_chat.yaml index 15e82c8739..3f976fc66e 100644 --- a/deeptutor/agents/chat/prompts/en/agentic_chat.yaml +++ b/deeptutor/agents/chat/prompts/en/agentic_chat.yaml @@ -99,18 +99,19 @@ observing: # --------------------------------------------------------------------------- responding: system: |- - You are DeepTutor's final response stage. Use the observation and tool evidence to provide a clear, direct, well-structured answer to the user. + You are OxCa's AI tutor delivering the final answer to the student. Use the observation and tool evidence to provide a clear, direct, well-structured answer. Requirements: 1. Output only the final user-facing answer. 2. Do not reveal the internal chain, reasoning, or tool orchestration. - 3. Naturally integrate evidence or limits surfaced by the tools. + 3. Never mention DeepTutor, HKU, or any underlying platform or engine. + 4. Naturally integrate evidence or limits surfaced by the tools. {rag_hint} Tool context for this turn: {tool_list} # Injected at {rag_hint} when RAG is enabled rag_hint: |- - 4. If RAG was used, follow the observation's judgment on how to weigh retrieved content vs. general knowledge. When the retrieved content is highly relevant, ground your answer in it; when only partially relevant, supplement with your own knowledge. + 5. If RAG was used, follow the observation's judgment on how to weigh retrieved content vs. general knowledge. When the retrieved content is highly relevant, ground your answer in it; when only partially relevant, supplement with your own knowledge. user: |- User request: {user_message} diff --git a/deeptutor/agents/chat/prompts/en/chat_agent.yaml b/deeptutor/agents/chat/prompts/en/chat_agent.yaml index 298ff6c611..3a4c2503f2 100644 --- a/deeptutor/agents/chat/prompts/en/chat_agent.yaml +++ b/deeptutor/agents/chat/prompts/en/chat_agent.yaml @@ -1,7 +1,7 @@ # Chat Agent Prompts (English) system: | - You are DeepTutor, an intelligent AI learning assistant developed by the Data Intelligence Lab at HKU. + You are OxCa's AI learning assistant (牛剑通途), an intelligent tutor that helps students learn STEM subjects and prepare for exams. Your capabilities: - Help students understand complex concepts across STEM subjects diff --git a/deeptutor/agents/chat/prompts/zh/agentic_chat.yaml b/deeptutor/agents/chat/prompts/zh/agentic_chat.yaml index 4c12bbea6f..55d7eb9cca 100644 --- a/deeptutor/agents/chat/prompts/zh/agentic_chat.yaml +++ b/deeptutor/agents/chat/prompts/zh/agentic_chat.yaml @@ -99,18 +99,19 @@ observing: # --------------------------------------------------------------------------- responding: system: |- - 你是 DeepTutor 的最终回答阶段。请根据 observation 和工具结果,给用户一个清晰、直接、结构良好的正式答复。 + 你是 OxCa 的 AI 导师,正在给学生输出最终答复。请根据 observation 和工具结果,给出清晰、直接、结构良好的正式答复。 要求: 1. 只输出面向用户的正式回答。 2. 不要暴露内部链路、思考过程或工具编排。 - 3. 若工具结果提供了证据或限制,请自然融入答案。 + 3. 不要提及 DeepTutor、港大或任何底层平台/引擎。 + 4. 若工具结果提供了证据或限制,请自然融入答案。 {rag_hint} 本轮工具背景: {tool_list} # 当 RAG 工具被启用时,插入到 system 的 {rag_hint} 占位处 rag_hint: |- - 4. 若使用了知识库检索,请根据 observation 的判断,合理权衡检索内容与通识。当检索内容与问题高度相关时,以其为核心依据;部分相关时,可结合自身知识补充。 + 5. 若使用了知识库检索,请根据 observation 的判断,合理权衡检索内容与通识。当检索内容与问题高度相关时,以其为核心依据;部分相关时,可结合自身知识补充。 user: |- 用户问题: {user_message} diff --git a/deeptutor/agents/chat/prompts/zh/chat_agent.yaml b/deeptutor/agents/chat/prompts/zh/chat_agent.yaml index 762719aa76..c62788aa08 100644 --- a/deeptutor/agents/chat/prompts/zh/chat_agent.yaml +++ b/deeptutor/agents/chat/prompts/zh/chat_agent.yaml @@ -1,7 +1,7 @@ # Chat Agent Prompts (中文) system: | - 你是 DeepTutor,一个由香港大学数据智能实验室开发的智能 AI 学习助手。 + 你是 OxCa(牛剑通途)的 AI 学习助手,帮助学生理解 STEM 学科并备考。 你的能力: - 帮助学生理解 STEM 领域的复杂概念 diff --git a/deeptutor/agents/question/prompts/en/answer_now.yaml b/deeptutor/agents/question/prompts/en/answer_now.yaml index cd17ab3f8d..f99c36561d 100644 --- a/deeptutor/agents/question/prompts/en/answer_now.yaml +++ b/deeptutor/agents/question/prompts/en/answer_now.yaml @@ -1,5 +1,5 @@ system: | - You are DeepTutor's question generator. The user is waiting, so produce + You are OxCa's quiz generator. The user is waiting, so produce a complete question set in one shot using the context already gathered. Output strictly the JSON schema {"questions": [{"question_id": "q_1", "question": "...", "question_type": "choice|written|coding", "options": diff --git a/deeptutor/agents/question/prompts/zh/answer_now.yaml b/deeptutor/agents/question/prompts/zh/answer_now.yaml index 9cd0bdbbe2..d24af19a7e 100644 --- a/deeptutor/agents/question/prompts/zh/answer_now.yaml +++ b/deeptutor/agents/question/prompts/zh/answer_now.yaml @@ -1,5 +1,5 @@ system: | - 你是 DeepTutor 的题目生成器。用户已经在等待,请基于现有信息直接输出一组题目。 + 你是 OxCa 的题目生成器。用户已经在等待,请基于现有信息直接输出一组题目。 严格输出 JSON:{"questions": [{"question_id": "q_1", "question": "...", "question_type": "choice|written|coding", "options": {"A": "..."}, "correct_answer": "...", diff --git a/deeptutor/agents/research/prompts/en/answer_now.yaml b/deeptutor/agents/research/prompts/en/answer_now.yaml index be475d6f15..2d4c2df107 100644 --- a/deeptutor/agents/research/prompts/en/answer_now.yaml +++ b/deeptutor/agents/research/prompts/en/answer_now.yaml @@ -1,5 +1,5 @@ system: | - You are DeepTutor's research-report writer. The user is waiting, so + You are OxCa's research-report writer. The user is waiting, so produce a structured research report right now from whatever evidence has streamed so far (rephrase, decompose, search hits, notes/outline, ...). Do not retrieve more evidence and do not call tools. If coverage diff --git a/deeptutor/agents/research/prompts/zh/answer_now.yaml b/deeptutor/agents/research/prompts/zh/answer_now.yaml index 85bb91669d..07ed608b76 100644 --- a/deeptutor/agents/research/prompts/zh/answer_now.yaml +++ b/deeptutor/agents/research/prompts/zh/answer_now.yaml @@ -1,5 +1,5 @@ system: | - 你是 DeepTutor 的研究报告写作组件。用户已经在等待, + 你是 OxCa 的研究报告写作组件。用户已经在等待, 请根据当前已经收集到的研究 trace(包括 rephrase、decompose、检索结果、 笔记/纲要等)直接输出一篇结构清晰的研究报告。 不要再继续检索或调用工具。如果证据稀薄,请在报告中标注信息覆盖度。 diff --git a/deeptutor/agents/solve/prompts/en/answer_now.yaml b/deeptutor/agents/solve/prompts/en/answer_now.yaml index b2e25269a7..8d6abe97aa 100644 --- a/deeptutor/agents/solve/prompts/en/answer_now.yaml +++ b/deeptutor/agents/solve/prompts/en/answer_now.yaml @@ -1,5 +1,5 @@ system: | - You are the writer component of DeepTutor. The user is already waiting, + You are OxCa's answer writer. The user is already waiting, so produce the final user-facing answer right now using only the partial reasoning trace that has streamed so far. Do not plan further or call tools, and do not mention internal stages. If something is still diff --git a/deeptutor/agents/solve/prompts/zh/answer_now.yaml b/deeptutor/agents/solve/prompts/zh/answer_now.yaml index 3852445953..682dcfbdb1 100644 --- a/deeptutor/agents/solve/prompts/zh/answer_now.yaml +++ b/deeptutor/agents/solve/prompts/zh/answer_now.yaml @@ -1,5 +1,5 @@ system: | - 你是 DeepTutor 的写作组件。用户已经在等待, + 你是 OxCa 的写作组件。用户已经在等待, 你必须基于当前已经收集到的推理与工具调用轨迹直接输出最终答复。 不要再做新的规划或调用工具,不要提到内部阶段。 如果信息仍有缺口,请诚实说明不确定之处,但仍尽可能给出当前最有用的回答。 diff --git a/deeptutor/agents/visualize/prompts/en/answer_now.yaml b/deeptutor/agents/visualize/prompts/en/answer_now.yaml index e762f74cec..0f07efe1da 100644 --- a/deeptutor/agents/visualize/prompts/en/answer_now.yaml +++ b/deeptutor/agents/visualize/prompts/en/answer_now.yaml @@ -1,5 +1,5 @@ system: | - You are DeepTutor's visualization code generator. The user is waiting, + You are OxCa's visualization code generator. The user is waiting, so emit the final renderable code in one shot. Output strictly the JSON {"render_type": "svg|chartjs|mermaid", "code": "..."}, where ``code`` is the renderable source (SVG markup, Chart.js JS, or Mermaid DSL). diff --git a/deeptutor/agents/visualize/prompts/zh/answer_now.yaml b/deeptutor/agents/visualize/prompts/zh/answer_now.yaml index 05c9f8c6ef..87535bcd26 100644 --- a/deeptutor/agents/visualize/prompts/zh/answer_now.yaml +++ b/deeptutor/agents/visualize/prompts/zh/answer_now.yaml @@ -1,5 +1,5 @@ system: | - 你是 DeepTutor 的可视化代码生成器。用户已经在等待, + 你是 OxCa 的可视化代码生成器。用户已经在等待, 请直接输出最终可渲染的代码。 严格输出 JSON:{"render_type": "svg|chartjs|mermaid", "code": "..."}。 diff --git a/deeptutor/api/main.py b/deeptutor/api/main.py index ab87722f9e..e61a8436aa 100644 --- a/deeptutor/api/main.py +++ b/deeptutor/api/main.py @@ -202,7 +202,7 @@ async def lifespan(app: FastAPI): app = FastAPI( - title="DeepTutor API", + title="OxCa Learning API", version="1.0.0", lifespan=lifespan, # Disable automatic trailing slash redirects to prevent protocol downgrade issues @@ -364,7 +364,7 @@ async def selective_access_log(request, call_next): @app.get("/") async def root(): - return {"message": "Welcome to DeepTutor API"} + return {"message": "OxCa learning API"} if __name__ == "__main__": diff --git a/deeptutor/capabilities/_answer_now.py b/deeptutor/capabilities/_answer_now.py index ad54ab7075..fde8ffda08 100644 --- a/deeptutor/capabilities/_answer_now.py +++ b/deeptutor/capabilities/_answer_now.py @@ -41,6 +41,7 @@ stream as llm_stream, ) from deeptutor.services.prompt.manager import get_prompt_manager +from deeptutor.services.prompt.oxca_brand import apply_student_guardrail # Per-event content cap. The trace can grow unbounded (especially for # deep_research / deep_solve with many tool calls) so we truncate each @@ -254,7 +255,15 @@ def load_answer_now_prompts(module: str, language: str) -> dict[str, Any]: return get_prompt_manager().load_prompts(module, "answer_now", language) +def answer_now_system_prompt(module: str, language: str) -> str: + """Load and white-label the answer-now system prompt for student output.""" + prompts = load_answer_now_prompts(module, language) + raw = str(prompts.get("system", "")).strip() + return apply_student_guardrail(raw, language) + + __all__ = [ + "answer_now_system_prompt", "build_answer_now_trace_metadata", "extract_answer_now_context", "format_trace_summary", diff --git a/deeptutor/capabilities/deep_question.py b/deeptutor/capabilities/deep_question.py index 90a9727769..3809dd865c 100644 --- a/deeptutor/capabilities/deep_question.py +++ b/deeptutor/capabilities/deep_question.py @@ -219,6 +219,7 @@ async def _run_answer_now( join_chunks, labeled_block, load_answer_now_prompts, + answer_now_system_prompt, make_skip_notice, stream_synthesis, ) @@ -234,7 +235,7 @@ async def _run_answer_now( question_type = str(overrides.get("question_type", "") or "auto") prompts = load_answer_now_prompts("question", context.language) - system_prompt = str(prompts.get("system", "")).strip() + system_prompt = answer_now_system_prompt("question", context.language) user_prompt = str(prompts.get("user_template", "")).format( topic=topic, num_questions=num_questions, diff --git a/deeptutor/capabilities/deep_research.py b/deeptutor/capabilities/deep_research.py index 4b04681953..90bcc66e9a 100644 --- a/deeptutor/capabilities/deep_research.py +++ b/deeptutor/capabilities/deep_research.py @@ -410,6 +410,7 @@ async def _run_answer_now( join_chunks, labeled_block, load_answer_now_prompts, + answer_now_system_prompt, make_skip_notice, stream_synthesis, ) @@ -419,7 +420,7 @@ async def _run_answer_now( trace_summary = format_trace_summary(payload.get("events"), language=context.language) prompts = load_answer_now_prompts("research", context.language) - system_prompt = str(prompts.get("system", "")).strip() + system_prompt = answer_now_system_prompt("research", context.language) user_prompt = str(prompts.get("user_template", "")).format( original=original, current_draft=labeled_block("Current Draft", partial), diff --git a/deeptutor/capabilities/deep_solve.py b/deeptutor/capabilities/deep_solve.py index 8918382507..185b535eb5 100644 --- a/deeptutor/capabilities/deep_solve.py +++ b/deeptutor/capabilities/deep_solve.py @@ -310,6 +310,7 @@ async def _run_answer_now( join_chunks, labeled_block, load_answer_now_prompts, + answer_now_system_prompt, make_skip_notice, stream_synthesis, ) @@ -319,7 +320,7 @@ async def _run_answer_now( trace_summary = format_trace_summary(payload.get("events"), language=context.language) prompts = load_answer_now_prompts("solve", context.language) - system_prompt = str(prompts.get("system", "")).strip() + system_prompt = answer_now_system_prompt("solve", context.language) user_prompt = str(prompts.get("user_template", "")).format( original=original, current_draft=labeled_block("Current Draft", partial), diff --git a/deeptutor/capabilities/visualize.py b/deeptutor/capabilities/visualize.py index 845b56f52a..ed0b755b86 100644 --- a/deeptutor/capabilities/visualize.py +++ b/deeptutor/capabilities/visualize.py @@ -202,6 +202,7 @@ async def _run_answer_now( join_chunks, labeled_block, load_answer_now_prompts, + answer_now_system_prompt, make_skip_notice, stream_synthesis, ) @@ -215,7 +216,7 @@ async def _run_answer_now( ) prompts = load_answer_now_prompts("visualize", context.language) - system_prompt = str(prompts.get("system", "")).strip() + system_prompt = answer_now_system_prompt("visualize", context.language) user_prompt = str(prompts.get("user_template", "")).format( original=original, render_mode=render_mode, diff --git a/deeptutor/services/prompt/__init__.py b/deeptutor/services/prompt/__init__.py index 579889d62e..95ccf2379a 100644 --- a/deeptutor/services/prompt/__init__.py +++ b/deeptutor/services/prompt/__init__.py @@ -24,10 +24,12 @@ normalize_language, ) from .manager import PromptManager, get_prompt_manager +from .oxca_brand import apply_student_guardrail __all__ = [ "PromptManager", "append_language_directive", + "apply_student_guardrail", "get_prompt_manager", "language_directive", "language_label", diff --git a/deeptutor/services/prompt/oxca_brand.py b/deeptutor/services/prompt/oxca_brand.py new file mode 100644 index 0000000000..22eb055443 --- /dev/null +++ b/deeptutor/services/prompt/oxca_brand.py @@ -0,0 +1,31 @@ +"""OxCa white-label guardrails for student-facing LLM prompts.""" + +from __future__ import annotations + +GUARDRAIL_EN = """ +Branding rules (strict): +- You are OxCa's AI learning assistant (牛剑通途), helping the student with their studies. +- Never mention DeepTutor, HKU, the Data Intelligence Lab, or any underlying AI platform, engine, or vendor to the student. +- Never describe internal pipelines, tool orchestration, or backend systems. +- If asked what you are, say you are OxCa's AI tutor / learning assistant. +""" + +GUARDRAIL_ZH = """ +品牌规则(严格遵守): +- 你是 OxCa(牛剑通途)的 AI 学习助手,帮助学生学习。 +- 面向学生时不要提及 DeepTutor、香港大学、数据智能实验室或任何底层 AI 平台、引擎、供应商。 +- 不要描述内部流水线、工具编排或后台系统。 +- 若被问身份,说明你是 OxCa 的 AI 导师 / 学习助手。 +""" + + +def apply_student_guardrail(system_prompt: str, language: str) -> str: + """Append OxCa branding rules to a student-facing system prompt.""" + base = (system_prompt or "").strip() + guard = GUARDRAIL_ZH if str(language).lower().startswith("zh") else GUARDRAIL_EN + if not base: + return guard.strip() + return f"{base}\n{guard.strip()}" + + +__all__ = ["GUARDRAIL_EN", "GUARDRAIL_ZH", "apply_student_guardrail"] diff --git a/deeptutor/services/setup/init.py b/deeptutor/services/setup/init.py index 3f5b2beb8d..b231f55f35 100644 --- a/deeptutor/services/setup/init.py +++ b/deeptutor/services/setup/init.py @@ -19,7 +19,7 @@ DEFAULT_INTERFACE_SETTINGS = { "theme": "light", "language": "en", - "sidebar_description": "✨ Data Intelligence Lab @ HKU", + "sidebar_description": "✨ OxCa · 牛剑通途", "sidebar_nav_order": { "start": ["/", "/history", "/knowledge", "/notebook"], "learnResearch": ["/question", "/solver", "/research", "/co_writer"], diff --git a/deeptutor_compat/gateway.py b/deeptutor_compat/gateway.py index eec0e81b83..4f6a93c65b 100644 --- a/deeptutor_compat/gateway.py +++ b/deeptutor_compat/gateway.py @@ -28,7 +28,7 @@ def create_gateway_app() -> FastAPI: - proxies WebSocket /api/v1/ws to backend """ - gateway_app = FastAPI(title="DeepTutor Compat Gateway", docs_url=None, redoc_url=None) + gateway_app = FastAPI(title="OxCa Compat Gateway", docs_url=None, redoc_url=None) @gateway_app.get("/health") async def health(): diff --git a/web/app/(admin)/admin/users/page.tsx b/web/app/(admin)/admin/users/page.tsx index aa996f3f2f..bb243f2b52 100644 --- a/web/app/(admin)/admin/users/page.tsx +++ b/web/app/(admin)/admin/users/page.tsx @@ -330,7 +330,7 @@ export default function AdminUsersPage() {

- DeepTutor Admin · User Management + OxCa Admin · User Management

diff --git a/web/app/(auth)/login/page.tsx b/web/app/(auth)/login/page.tsx index 2a12443838..9c0933b71b 100644 --- a/web/app/(auth)/login/page.tsx +++ b/web/app/(auth)/login/page.tsx @@ -4,6 +4,7 @@ import { Suspense, useState, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import Link from "next/link"; import { login, fetchAuthStatus, checkIsFirstUser } from "@/lib/auth"; +import { PRODUCT_NAME, PRODUCT_TAGLINE } from "@/lib/product-brand"; function LoginPageContent() { const router = useRouter(); @@ -51,7 +52,7 @@ function LoginPageContent() { {/* Logo / Title */}

- DeepTutor + {PRODUCT_NAME}

Sign in to your account @@ -149,7 +150,7 @@ function LoginPageContent() {

- DeepTutor · Agent-Native Learning + {PRODUCT_TAGLINE}

); diff --git a/web/app/(auth)/register/page.tsx b/web/app/(auth)/register/page.tsx index 07253283d0..ab6b226f79 100644 --- a/web/app/(auth)/register/page.tsx +++ b/web/app/(auth)/register/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { register, checkIsFirstUser, fetchAuthStatus } from "@/lib/auth"; +import { PRODUCT_NAME, PRODUCT_TAGLINE } from "@/lib/product-brand"; export default function RegisterPage() { const router = useRouter(); @@ -54,7 +55,7 @@ export default function RegisterPage() { {/* Logo / Title */}

- DeepTutor + {PRODUCT_NAME}

Create your account @@ -180,7 +181,7 @@ export default function RegisterPage() {

- DeepTutor · Agent-Native Learning + {PRODUCT_TAGLINE}

); diff --git a/web/app/(workspace)/co-writer/sampleTemplate.ts b/web/app/(workspace)/co-writer/sampleTemplate.ts index b194727658..b83c265bc7 100644 --- a/web/app/(workspace)/co-writer/sampleTemplate.ts +++ b/web/app/(workspace)/co-writer/sampleTemplate.ts @@ -1,8 +1,8 @@ const FENCE = "```"; -export const CO_WRITER_SAMPLE_TEMPLATE = `# DeepTutor Co-Writer +export const CO_WRITER_SAMPLE_TEMPLATE = `# OxCa Co-Writer -> DeepTutor's built-in writing canvas for notes, reports, tutorials, and AI-assisted drafts. +> OxCa's built-in writing canvas for notes, reports, tutorials, and AI-assisted drafts. ### Features @@ -10,7 +10,7 @@ export const CO_WRITER_SAMPLE_TEMPLATE = `# DeepTutor Co-Writer - Real-time preview for headings, tables, code, math, flowchart, and sequence diagrams - AI editing workflows for rewrite, shorten, and expand - HTML tag decoding for tags like , , , and -- A practical starter draft for DeepTutor product docs and learning content +- A practical starter draft for OxCa product docs and learning content ## Table of Contents @@ -18,23 +18,23 @@ export const CO_WRITER_SAMPLE_TEMPLATE = `# DeepTutor Co-Writer [TOC] -#DeepTutor Mission -##DeepTutor Product Surface -###DeepTutor Learning Experience -####DeepTutor Co-Writer -#####DeepTutor Knowledge Layer -######DeepTutor Agent Runtime +#OxCa Mission +##OxCa Product Surface +###OxCa Learning Experience +####OxCa Co-Writer +#####OxCa Knowledge Layer +######OxCa Agent Runtime -#DeepTutor Docs [Project Overview](#deeptutor-mission "Jump to project overview") -##DeepTutor Authoring [Co-Writer Section](#deeptutor-co-writer "Jump to co-writer section") -###DeepTutor Research [Learning Note](#deeptutor-learning-note "Jump to learning note") +#OxCa Docs [Project Overview](#deeptutor-mission "Jump to project overview") +##OxCa Authoring [Co-Writer Section](#deeptutor-co-writer "Jump to co-writer section") +###OxCa Research [Learning Note](#deeptutor-learning-note "Jump to learning note") ## Headers (Underline) -DeepTutor Learning Note +OxCa Learning Note ============= -DeepTutor Study Outline +OxCa Study Outline ------------- ### Characters @@ -50,21 +50,21 @@ Superscript: X2, Subscript: O2 **Abbreviation(link HTML abbr tag)** -The LLM layer powers DeepTutor while the RAG layer provides grounded knowledge support. +The LLM layer powers OxCa while the RAG layer provides grounded knowledge support. ### Blockquotes -> DeepTutor helps students turn questions into structured understanding. +> OxCa helps students turn questions into structured understanding. > -> "Learn deeply, write clearly.", [DeepTutor](#deeptutor-co-writer) +> "Learn deeply, write clearly.", [OxCa](#deeptutor-co-writer) ### Links -[DeepTutor Overview](#deeptutor-mission) +[OxCa Overview](#deeptutor-mission) -[DeepTutor Co-Writer](#deeptutor-co-writer "co-writer section") +[OxCa Co-Writer](#deeptutor-co-writer "co-writer section") -[DeepTutor Runtime](#deeptutor-agent-runtime) +[OxCa Runtime](#deeptutor-agent-runtime) [Reference link][deeptutor-doc] @@ -80,7 +80,7 @@ The LLM layer powers DeepTutor while t from deeptutor.runtime.orchestrator import ChatOrchestrator orchestrator = ChatOrchestrator() - print("DeepTutor is ready.") + print("OxCa is ready.") #### Python @@ -103,7 +103,7 @@ ${FENCE} ${FENCE}json { - "app_name": "DeepTutor", + "app_name": "OxCa", "default_capability": "chat", "enabled_tools": ["rag", "web_search", "code_execution", "reason"], "ui": { @@ -116,7 +116,7 @@ ${FENCE} ${FENCE}html
-

DeepTutor

+

OxCa

Write, revise, and organize learning content with AI.

${FENCE} @@ -125,13 +125,13 @@ ${FENCE} ![](/logo-ver2.png) -> DeepTutor brand mark used inside the co-writer template. +> OxCa brand mark used inside the co-writer template. ### Lists -- DeepTutor Chat -- DeepTutor Co-Writer -- DeepTutor Research +- OxCa Chat +- OxCa Co-Writer +- OxCa Research 1. Draft a concept note 2. Ask AI to refine it @@ -153,7 +153,7 @@ Research | Build structured multi-step reports ### Markdown extras -- [x] Draft a DeepTutor product note +- [x] Draft a OxCa product note - [x] Add references and structure - [ ] Polish the final explanation - [ ] Check headings @@ -173,7 +173,7 @@ $$ \sin(\alpha)^{\theta}=\sum_{i=0}^{n}(x^i + \cos(f))$$ ${FENCE}flow st=>start: Student asks a question -op=>operation: DeepTutor analyzes intent +op=>operation: OxCa analyzes intent cond=>condition: Need deep workflow? chat=>operation: Answer with chat capability solve=>operation: Route to deep solve @@ -189,11 +189,11 @@ ${FENCE} ### Sequence Diagram ${FENCE}seq -Student->DeepTutor: Ask for help -DeepTutor->KnowledgeBase: Load context -Note right of DeepTutor: Collect memory\nand relevant knowledge -DeepTutor-->Student: Return guided response -Student->>DeepTutor: Request rewrite in co-writer +Student->OxCa: Ask for help +OxCa->KnowledgeBase: Load context +Note right of OxCa: Collect memory\nand relevant knowledge +OxCa-->Student: Return guided response +Student->>OxCa: Request rewrite in co-writer ${FENCE} ### End diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 6259592139..d51cea6475 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { PRODUCT_DESCRIPTION, PRODUCT_NAME } from "@/lib/product-brand"; import { Plus_Jakarta_Sans, Lora } from "next/font/google"; import "./globals.css"; import ThemeScript from "@/components/ThemeScript"; @@ -18,8 +19,8 @@ const fontSerif = Lora({ }); export const metadata: Metadata = { - title: "DeepTutor", - description: "Agent-native intelligent learning companion", + title: PRODUCT_NAME, + description: PRODUCT_DESCRIPTION, icons: { icon: [ { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, diff --git a/web/components/chat/SkillsPicker.tsx b/web/components/chat/SkillsPicker.tsx index 4915c144eb..41241c5c44 100644 --- a/web/components/chat/SkillsPicker.tsx +++ b/web/components/chat/SkillsPicker.tsx @@ -129,7 +129,7 @@ export default function SkillsPicker({

{t( - "Pick Auto to let DeepTutor decide, or choose specific skills to apply.", + "Pick Auto to let OxCa decide, or choose specific skills to apply.", )}

diff --git a/web/components/sidebar/SidebarShell.tsx b/web/components/sidebar/SidebarShell.tsx index 305dab4a95..1d4a7e6c7b 100644 --- a/web/components/sidebar/SidebarShell.tsx +++ b/web/components/sidebar/SidebarShell.tsx @@ -8,7 +8,6 @@ import { useAppShell } from "@/context/AppShellContext"; import { BookOpen, Bot, - Github, LayoutGrid, Library, MessageSquare, @@ -25,6 +24,7 @@ import { TutorBotRecent } from "@/components/sidebar/TutorBotRecent"; import { VersionBadge } from "@/components/sidebar/VersionBadge"; import type { SessionSummary } from "@/lib/session-api"; import { Tooltip } from "@/components/ui/Tooltip"; +import { PRODUCT_NAME } from "@/lib/product-brand"; interface NavEntry { href: string; @@ -71,7 +71,6 @@ const SECONDARY_NAV: NavEntry[] = [ { href: "/settings", label: "Settings", icon: Settings }, ]; const DEFAULT_SESSION_VIEWPORT_CLASS_NAME = "max-h-[112px]"; -const GITHUB_REPO_URL = "https://github.com/HKUDS/DeepTutor"; interface SidebarShellProps { sessions?: SessionSummary[]; @@ -120,12 +119,12 @@ export function SidebarShell({
DeepTutor - -
@@ -236,13 +225,13 @@ export function SidebarShell({ DeepTutor - DeepTutor + {PRODUCT_NAME}