mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-21 03:46:11 +00:00
96b041846d
* feat: add mcp and skills
* feat: add filter
* feat: modify frontend
* feat(box): add sandbox_exec tool loop for local-agent calculations
* feat(box): add host workspace mounting and sandbox_exec guidance
* feat(box): add BoxProfile with resource limits and improved output truncation
- Implement head+tail output truncation (60/40 split) so LLM sees both
beginning and final results; add streaming byte-limited reads in backend
to prevent unbounded memory usage (_MAX_RAW_OUTPUT_BYTES = 1MB)
- Define BoxProfile model with locked fields and max_timeout_sec clamping
- Add four built-in profiles: default, offline_readonly, network_basic,
network_extended with differentiated resource and security constraints
- Add resource limit fields to BoxSpec (cpus, memory_mb, pids_limit,
read_only_rootfs) and pass corresponding container CLI flags
(--cpus, --memory, --pids-limit, --read-only, --tmpfs)
- Profile loaded from config (box.profile), applied in service layer
before BoxSpec validation; locked fields cannot be overridden by
tool-call parameters
* feat(box): add obs
* refactor(box): unify box service lifecycle and local runtime
management
* refactor(box): remove legacy in-process runtime code and clean up smells
After the architecture settled on always using an independent Box Runtime
service, several pieces of compatibility code and design shortcuts were
left behind. This commit cleans them up:
- Remove `LocalBoxRuntimeClient` and `create_box_runtime_client` from
production code (moved to test-only helper).
- Remove unused `_clip_bytes` method from backend.
- Remove `__langbot_session_placeholder__` hack by making `BoxSpec.cmd`
default to empty and validating non-empty only in `runtime.execute()`.
- Extract `get_box_config()` helper to eliminate 5× duplicated config
access boilerplate.
- Remove `session_id`/`host_path`/`host_path_mode` from the LLM-facing
tool schema to enforce request-scoped session isolation.
- Fix dual shutdown path: `NativeToolLoader.shutdown()` no longer calls
`box_service.shutdown()` (handled by `Application.dispose()`).
- Simplify `_assert_session_compatible` with a loop.
- Inline client creation in `BoxRuntimeConnector`.
- Remove redundant `BOX__RUNTIME_URL` env var from docker-compose
(auto-detected by code).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add test
* fix: fix box intergration test
* feat(box/mcp): integrate MCP stdio with Box sandbox — auto-isolation, dep install, security
## Summary
When Podman/Docker is available, all stdio-mode MCP servers now automatically
run inside Box containers with dependency installation, path rewriting, and
lifecycle management. When no container runtime exists, LangBot starts normally
and stdio MCP falls back to host-direct execution.
## What changed
### MCP stdio → Box integration (mcp.py)
- Add `MCPServerBoxConfig` pydantic model for structured box configuration
with validation and defaults (network, host_path_mode, timeouts, resources)
- Auto-infer `host_path` from command/args with venv detection: recognizes
`.venv/bin/python` patterns and walks up to the project root
- Rewrite host paths to container `/workspace` paths transparently
- Replace venv python commands with container-native `python`
- Auto-detect `pyproject.toml`/`setup.py`/`requirements.txt` and run
`pip install` inside the container before starting the MCP server
- Copy project to `/tmp` before install to handle read-only mounts
- Add retry with exponential backoff (3 retries, 2s/4s/8s delays)
- Add Box managed process health monitoring (poll every 5s)
- Fix session leak: `_cleanup_box_stdio_session()` now runs in `finally`
block of `_lifecycle_loop`, covering all exit paths
- Fix retry logic: `_ready_event` is only set after all retries exhaust
or on success, not on first failure
- Enhance `get_runtime_info_dict()` with `box_session_id` and `box_enabled`
### Box security (security.py — new)
- `validate_sandbox_security()` blocks dangerous host paths:
`/etc`, `/proc`, `/sys`, `/dev`, `/root`, `/boot`, `/run`,
docker.sock, podman socket
- Called at the start of `CLISandboxBackend.start_session()`
### Box models (models.py)
- Add `BoxHostMountMode.NONE` — skips volume mount entirely
- Adjust `validate_host_mount_consistency` to allow arbitrary workdir
when `host_path_mode=NONE`
### Box backend (backend.py)
- Add `validate_sandbox_security()` call in `start_session()`
- Add `langbot.box.config_hash` label on containers for drift detection
- Handle `BoxHostMountMode.NONE` — skip `-v` mount arg
- Add `cleanup_orphaned_containers()` to base class (no-op default) and
CLI implementation (single batched `rm -f` command)
### Box runtime (runtime.py)
- Call `cleanup_orphaned_containers()` during `initialize()` to remove
lingering containers from previous runs
### Box service (service.py)
- Graceful degradation: `initialize()` catches runtime errors and sets
`available=False` instead of crashing LangBot startup
- Add `available` property and guard on `execute_sandbox_tool()`
- Add `skip_host_mount_validation` parameter to `build_spec()` and
`create_session()` — MCP paths are admin-configured and trusted,
bypassing `allowed_host_mount_roots` restrictions meant for
LLM-generated sandbox_exec commands
### Default behavior
- stdio MCP servers automatically use Box when `box_service.available`
is True (Podman/Docker detected); no explicit `box` config needed
- When no container runtime exists, falls back to host-direct stdio
- MCP Box defaults: `network=on` (for pip install), `read_only_rootfs=false`
(for site-packages), `host_path_mode=ro`, `startup_timeout=120s`
### Tests
- `test_box_security.py`: blocked paths, safe paths, subpath rejection
- `test_mcp_box_integration.py`: config model, path rewriting, venv
unwrap, host_path inference, payload building, runtime info, box
availability check
- `test_box_service.py`: `BoxHostMountMode.NONE` validation tests
* feat(box/mcp): instance-based orphan cleanup, error classification, session API, and integration tests
## Changes
### Precise orphan container cleanup
- Runtime generates a unique instance_id on startup
- Every container gets a `langbot.box.instance_id` label
- `cleanup_orphaned_containers()` only removes containers from
previous instances, preserving containers owned by the current one
- Containers from older versions (no label) are also cleaned up
- `cleanup_orphaned_containers` added to `BaseSandboxBackend` as
a no-op default method, removing hasattr duck-typing
### Fine-grained MCP error classification
- New `MCPSessionErrorPhase` enum with 7 phases: session_create,
dep_install, process_start, relay_connect, mcp_init, runtime,
tool_call
- Each phase in `_init_box_stdio_server()` sets the error phase
before re-raising, enabling precise failure diagnosis
- `retry_count` tracked across retry attempts
- `get_runtime_info_dict()` exposes `error_phase` and `retry_count`
### GET /v1/sessions/{id} API
- `BoxRuntime.get_session()` returns session details including
managed process info when present
- `handle_get_session` HTTP handler + route in server.py
- `BoxRuntimeClient.get_session()` abstract method + remote impl
### stdio defaults to Box when runtime is available
- `_uses_box_stdio()` checks `box_service.available` instead of
requiring explicit `box` key in server_config
- `BoxService.initialize()` catches runtime errors gracefully,
sets `available=False` instead of crashing LangBot startup
- When no container runtime exists, stdio MCP falls back to
host-direct execution
### Code quality (from /simplify review)
- Extracted `_VENV_DIRS` / `_VENV_BIN_DIRS` module-level constants
- Removed dead `_box_network_mode()` method and unused `bc` variable
- Fixed broken import `from ....box.models` → `from ...box.models`
- Cached `_resolve_host_path()` result — computed once, passed through
- Config hash now includes `host_path` field
- Batched orphan cleanup into single `rm -f` command
### Session leak fix
- `_cleanup_box_stdio_session()` now runs in `_lifecycle_loop`'s
finally block, covering all exit paths (normal shutdown, error,
retry, final failure)
### Integration tests
- 6 end-to-end tests covering managed process lifecycle, WebSocket
stdio bidirectional IO, session cleanup verification, single
session query, process exit detection, and orphan cleanup safety
* refactor: use rpc
* fix: import
* refactor(box): clean up sandbox subsystem code quality and efficiency
- Fix O(n²) stderr trimming in runtime.py with running length tracker
- Remove dead code: RESERVED_CONTAINER_PATHS, _subprocess_wait_task,
unused config_hash computation, unused imports
- Deduplicate connection callback in BoxRuntimeConnector, parse URL once
- Use enum comparison instead of stringly-typed spec.network.value check
- Replace manual _result_to_dict/_session_to_dict with model_dump()
- Cache NativeToolLoader tool definition and sandbox system guidance
- Extract _is_path_under() helper to eliminate duplicated path checks
- Import SANDBOX_EXEC_TOOL_NAME from native.py instead of redefining
- Add JSON startswith guard in logging_utils to skip futile json.loads
- Fix ruff lint errors (F401 unused imports, F841 unused variables)
* fix: ruff
* refactor(sandbox): keep box logic out of pipeline and localagent
- Move sandbox system-prompt guidance from LocalAgentRunner into
BoxService.get_system_guidance() so all box domain knowledge stays
in the box module.
- Remove standalone logging_utils.py; merge format_result_log() into
MessageHandler base class alongside cut_str().
- Strip sandbox-specific JSON parsing from log formatting; tool
results now use generic truncation.
- Revert TYPE_CHECKING changes in stage.py and runner.py that were
unrelated to this feature.
- Skip two test files affected by a pre-existing circular import
(runner ↔ app) until the import cycle is resolved in a separate PR.
* fix: ruff
* refactor(box): move box runtime to langbot-plugin-sdk
Extract self-contained box runtime modules (actions, backend, client,
errors, models, runtime, security, server) to langbot-plugin-sdk and
update all imports to use `langbot_plugin.box.*`. Keep only service
and
connector in LangBot core as they depend on the Application context.
- Update docker-compose to use `langbot_plugin.box.server` entry
point
- Update pyproject.toml to use local SDK via `tool.uv.sources`
- Remove migrated source files and their unit/integration tests
- Update remaining test imports to match new module paths
* fix: ruff
* feat: enhance sandbox api
* refactor(box): derive paths from shared host root
* fix(box): tighten sandbox exposure and restore box integration coverage
* refactor(types): remove quoted annotations under postponed evaluation
* feat(box): unify native agent tools around exec/read/write/edit
* chore(sandbox): move MCP loader changes to follow-up branch
* feat(box): add session workspace quota enforcement and SDK quota metadata
* feat(skills): add Agent Skills management system (#1917)
* feat(skills): add Agent Skills management system
Implement comprehensive skills management feature inspired by agentskills spec:
Backend:
- Add Skill and SkillPipelineBinding database entities
- Add database migration (dbm018) for skills tables
- Implement SkillManager for skill loading, matching, and resolution
- Implement SkillService for CRUD operations
- Add skills API endpoints for skill and pipeline binding management
- Integrate skill index injection into pipeline preprocessor
- Add skill activation detection in LocalAgentRunner
Frontend:
- Add Skills page with listing, search, and type filter
- Add SkillDetailDialog for create/edit with preview
- Add SkillCard and SkillForm components
- Add skills API methods to BackendClient
- Add skills entry to sidebar navigation
- Add i18n translations (en-US, zh-Hans)
Features:
- Support skill and workflow types
- Sub-skill composition via {{INVOKE_SKILL: name}} syntax
- Progressive disclosure (index in prompt, full instructions on activation)
- Pipeline-specific skill bindings with priority
* fix: resolve cherry-pick conflicts for agentskills onto sandbox
- Remove non-existent external_kb service import
- Add skill_mgr mock to localagent sandbox_exec tests
- Keep database version at 24 (sandbox branch's latest)
* feat(skills): upgrade to package-backed skills with sandbox execution
Evolve the skills system from pure prompt-based to package-backed with
sandbox tool execution support:
- Add source_type/package_root/entry_file/skill_tools fields to Skill entity
- SkillManager loads SKILL.md from local package directories
- SkillToolLoader as 4th dispatch layer in ToolManager (query-scoped)
- LocalAgent injects skill tools into use_funcs on skill activation
- BoxService.execute_skill_tool() runs scripts in sandbox (ro mount, env params)
- Skill tool names auto-namespaced as skill__{skill}__{tool}
- API validation for package_root allowlist and entry path traversal
- Frontend source_type toggle, package_root input, skill_tools editor
- Migration renumbered to 025 with ALTER TABLE fallback for existing DBs
- Fix unclosed limitation section in i18n files
- Fix skills API methods misplaced outside BackendClient class
* fix: test info
* feat(skills): switch skills to package-backed storage and add import tooling
- skills 从 inline/package 双轨收敛成 package-first
- instructions 改为写入并读取 SKILL.md
- 新增本地目录扫描和 GitHub 安装 skill
- 前端把 skills 整合进 plugins 页,新增 SkillsComponent 和 GitHub 导入弹窗
- skill form 去掉 source_type / type 筛选,改成目录扫描驱动
- Box skill tool 挂载模式从 ro 改成 rw
- 测试和中英文文案同步更新
* feat: simplify langbot skill create and import
* refactor(skills): clean up legacy skill API and harden activation flow
* refactor(skills): remove skill dependency expansion and add skill_get
* fix: lint
* fix: delete
* fix(skills): align tool manager loader initialization
* refactor: remove sandbox execute skill
* fix(skills): hide activation markers and isolate skill activation flow
* refactor(skills): switch skill model to filesystem-backed packages
* refactor(skills): switch skill model to filesystem-backed packages
* refactor(skills): unify runtime skill access around filesystem paths
* refactor(skills): unify runtime skill access around filesystem paths
* feat(skills): align rw package design and fix skill activation, visibility, and lint issues
* refactor(skills): replace rich authoring API with import/reload flow and update
Box design doc
* feat(box): add sandbox_exec tool loop for local-agent calculations
* feat(box): add host workspace mounting and sandbox_exec guidance
* feat(box): add BoxProfile with resource limits and improved output truncation
- Implement head+tail output truncation (60/40 split) so LLM sees both
beginning and final results; add streaming byte-limited reads in backend
to prevent unbounded memory usage (_MAX_RAW_OUTPUT_BYTES = 1MB)
- Define BoxProfile model with locked fields and max_timeout_sec clamping
- Add four built-in profiles: default, offline_readonly, network_basic,
network_extended with differentiated resource and security constraints
- Add resource limit fields to BoxSpec (cpus, memory_mb, pids_limit,
read_only_rootfs) and pass corresponding container CLI flags
(--cpus, --memory, --pids-limit, --read-only, --tmpfs)
- Profile loaded from config (box.profile), applied in service layer
before BoxSpec validation; locked fields cannot be overridden by
tool-call parameters
* feat(box): add obs
* refactor(box): unify box service lifecycle and local runtime
management
* refactor(box): remove legacy in-process runtime code and clean up smells
After the architecture settled on always using an independent Box Runtime
service, several pieces of compatibility code and design shortcuts were
left behind. This commit cleans them up:
- Remove `LocalBoxRuntimeClient` and `create_box_runtime_client` from
production code (moved to test-only helper).
- Remove unused `_clip_bytes` method from backend.
- Remove `__langbot_session_placeholder__` hack by making `BoxSpec.cmd`
default to empty and validating non-empty only in `runtime.execute()`.
- Extract `get_box_config()` helper to eliminate 5× duplicated config
access boilerplate.
- Remove `session_id`/`host_path`/`host_path_mode` from the LLM-facing
tool schema to enforce request-scoped session isolation.
- Fix dual shutdown path: `NativeToolLoader.shutdown()` no longer calls
`box_service.shutdown()` (handled by `Application.dispose()`).
- Simplify `_assert_session_compatible` with a loop.
- Inline client creation in `BoxRuntimeConnector`.
- Remove redundant `BOX__RUNTIME_URL` env var from docker-compose
(auto-detected by code).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(box/mcp): integrate MCP stdio with Box sandbox — auto-isolation, dep install, security
## Summary
When Podman/Docker is available, all stdio-mode MCP servers now automatically
run inside Box containers with dependency installation, path rewriting, and
lifecycle management. When no container runtime exists, LangBot starts normally
and stdio MCP falls back to host-direct execution.
## What changed
### MCP stdio → Box integration (mcp.py)
- Add `MCPServerBoxConfig` pydantic model for structured box configuration
with validation and defaults (network, host_path_mode, timeouts, resources)
- Auto-infer `host_path` from command/args with venv detection: recognizes
`.venv/bin/python` patterns and walks up to the project root
- Rewrite host paths to container `/workspace` paths transparently
- Replace venv python commands with container-native `python`
- Auto-detect `pyproject.toml`/`setup.py`/`requirements.txt` and run
`pip install` inside the container before starting the MCP server
- Copy project to `/tmp` before install to handle read-only mounts
- Add retry with exponential backoff (3 retries, 2s/4s/8s delays)
- Add Box managed process health monitoring (poll every 5s)
- Fix session leak: `_cleanup_box_stdio_session()` now runs in `finally`
block of `_lifecycle_loop`, covering all exit paths
- Fix retry logic: `_ready_event` is only set after all retries exhaust
or on success, not on first failure
- Enhance `get_runtime_info_dict()` with `box_session_id` and `box_enabled`
### Box security (security.py — new)
- `validate_sandbox_security()` blocks dangerous host paths:
`/etc`, `/proc`, `/sys`, `/dev`, `/root`, `/boot`, `/run`,
docker.sock, podman socket
- Called at the start of `CLISandboxBackend.start_session()`
### Box models (models.py)
- Add `BoxHostMountMode.NONE` — skips volume mount entirely
- Adjust `validate_host_mount_consistency` to allow arbitrary workdir
when `host_path_mode=NONE`
### Box backend (backend.py)
- Add `validate_sandbox_security()` call in `start_session()`
- Add `langbot.box.config_hash` label on containers for drift detection
- Handle `BoxHostMountMode.NONE` — skip `-v` mount arg
- Add `cleanup_orphaned_containers()` to base class (no-op default) and
CLI implementation (single batched `rm -f` command)
### Box runtime (runtime.py)
- Call `cleanup_orphaned_containers()` during `initialize()` to remove
lingering containers from previous runs
### Box service (service.py)
- Graceful degradation: `initialize()` catches runtime errors and sets
`available=False` instead of crashing LangBot startup
- Add `available` property and guard on `execute_sandbox_tool()`
- Add `skip_host_mount_validation` parameter to `build_spec()` and
`create_session()` — MCP paths are admin-configured and trusted,
bypassing `allowed_host_mount_roots` restrictions meant for
LLM-generated sandbox_exec commands
### Default behavior
- stdio MCP servers automatically use Box when `box_service.available`
is True (Podman/Docker detected); no explicit `box` config needed
- When no container runtime exists, falls back to host-direct stdio
- MCP Box defaults: `network=on` (for pip install), `read_only_rootfs=false`
(for site-packages), `host_path_mode=ro`, `startup_timeout=120s`
### Tests
- `test_box_security.py`: blocked paths, safe paths, subpath rejection
- `test_mcp_box_integration.py`: config model, path rewriting, venv
unwrap, host_path inference, payload building, runtime info, box
availability check
- `test_box_service.py`: `BoxHostMountMode.NONE` validation tests
* feat(box/mcp): instance-based orphan cleanup, error classification, session API, and integration tests
## Changes
### Precise orphan container cleanup
- Runtime generates a unique instance_id on startup
- Every container gets a `langbot.box.instance_id` label
- `cleanup_orphaned_containers()` only removes containers from
previous instances, preserving containers owned by the current one
- Containers from older versions (no label) are also cleaned up
- `cleanup_orphaned_containers` added to `BaseSandboxBackend` as
a no-op default method, removing hasattr duck-typing
### Fine-grained MCP error classification
- New `MCPSessionErrorPhase` enum with 7 phases: session_create,
dep_install, process_start, relay_connect, mcp_init, runtime,
tool_call
- Each phase in `_init_box_stdio_server()` sets the error phase
before re-raising, enabling precise failure diagnosis
- `retry_count` tracked across retry attempts
- `get_runtime_info_dict()` exposes `error_phase` and `retry_count`
### GET /v1/sessions/{id} API
- `BoxRuntime.get_session()` returns session details including
managed process info when present
- `handle_get_session` HTTP handler + route in server.py
- `BoxRuntimeClient.get_session()` abstract method + remote impl
### stdio defaults to Box when runtime is available
- `_uses_box_stdio()` checks `box_service.available` instead of
requiring explicit `box` key in server_config
- `BoxService.initialize()` catches runtime errors gracefully,
sets `available=False` instead of crashing LangBot startup
- When no container runtime exists, stdio MCP falls back to
host-direct execution
### Code quality (from /simplify review)
- Extracted `_VENV_DIRS` / `_VENV_BIN_DIRS` module-level constants
- Removed dead `_box_network_mode()` method and unused `bc` variable
- Fixed broken import `from ....box.models` → `from ...box.models`
- Cached `_resolve_host_path()` result — computed once, passed through
- Config hash now includes `host_path` field
- Batched orphan cleanup into single `rm -f` command
### Session leak fix
- `_cleanup_box_stdio_session()` now runs in `_lifecycle_loop`'s
finally block, covering all exit paths (normal shutdown, error,
retry, final failure)
### Integration tests
- 6 end-to-end tests covering managed process lifecycle, WebSocket
stdio bidirectional IO, session cleanup verification, single
session query, process exit detection, and orphan cleanup safety
* refactor: use rpc
* fix: import
* refactor(box): clean up sandbox subsystem code quality and efficiency
- Fix O(n²) stderr trimming in runtime.py with running length tracker
- Remove dead code: RESERVED_CONTAINER_PATHS, _subprocess_wait_task,
unused config_hash computation, unused imports
- Deduplicate connection callback in BoxRuntimeConnector, parse URL once
- Use enum comparison instead of stringly-typed spec.network.value check
- Replace manual _result_to_dict/_session_to_dict with model_dump()
- Cache NativeToolLoader tool definition and sandbox system guidance
- Extract _is_path_under() helper to eliminate duplicated path checks
- Import SANDBOX_EXEC_TOOL_NAME from native.py instead of redefining
- Add JSON startswith guard in logging_utils to skip futile json.loads
- Fix ruff lint errors (F401 unused imports, F841 unused variables)
* fix: ruff
* refactor(sandbox): keep box logic out of pipeline and localagent
- Move sandbox system-prompt guidance from LocalAgentRunner into
BoxService.get_system_guidance() so all box domain knowledge stays
in the box module.
- Remove standalone logging_utils.py; merge format_result_log() into
MessageHandler base class alongside cut_str().
- Strip sandbox-specific JSON parsing from log formatting; tool
results now use generic truncation.
- Revert TYPE_CHECKING changes in stage.py and runner.py that were
unrelated to this feature.
- Skip two test files affected by a pre-existing circular import
(runner ↔ app) until the import cycle is resolved in a separate PR.
* refactor(box): move box runtime to langbot-plugin-sdk
Extract self-contained box runtime modules (actions, backend, client,
errors, models, runtime, security, server) to langbot-plugin-sdk and
update all imports to use `langbot_plugin.box.*`. Keep only service
and
connector in LangBot core as they depend on the Application context.
- Update docker-compose to use `langbot_plugin.box.server` entry
point
- Update pyproject.toml to use local SDK via `tool.uv.sources`
- Remove migrated source files and their unit/integration tests
- Update remaining test imports to match new module paths
* fix: ruff
* fix(box): tighten sandbox exposure and restore box integration coverage
* refactor(types): remove quoted annotations under postponed evaluation
* chore(sandbox): move MCP loader changes to follow-up branch
* refactor(plugins): simplify GitHub install flow to default master archive
* revert(api): restore plugin GitHub import flow in plugins controller
* Improve data-root handling and skill install previews
* Add managed skill authoring tools for local agents
* Refactor the skills UI around sidebar detail pages
* Document why managed skill authoring tools bypass box
* fix: lint
* feat(web): refactor plugin/skill install flows and fix skills page
- Fix sidebar skill icon
- Add skills route and error page component
- Refactor plugin GitHub install from dialog modal to inline card
- Add skill install dropdown menu (create/upload/github) in sidebar
- Wire sidebar → skills page communication via pendingSkillInstallAction context
- Add i18n keys for error page and skill install actions
* fix(web): persist sidebar collapsible section open state on navigation
Sections opened via sub-item navigation now retain their expanded state
when the user switches to a different section, instead of collapsing
because the isActive fallback becomes false.
---------
Co-authored-by: youhuanghe <1051233107@qq.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
* feat(sandbox): add MCP box integration on top of sandbox base (#2083)
* refactor(mcp): extract box stdio runtime helper
* refactor(box): introduce reusable workspace session helper
* refactor(box): run Box Runtime as subprocess inside LangBot container
Remove the separate langbot_box_runtime Docker service. Box Runtime
now always launches as a local stdio subprocess, regardless of whether
LangBot runs in Docker or not. The WebSocket transport path is kept
only for explicit runtime_url configuration (remote deployment).
This simplifies deployment by eliminating cross-container path mapping
and network hops. Box Runtime is a pure scheduling process (talks to
Docker socket / nsjail), it does not execute user code or touch the
filesystem, so container isolation is unnecessary — unlike Plugin
Runtime.
* fix(web): prevent first-emission snapshot from swallowing unsaved changes in pipeline editor
When switching runner (e.g. local-agent → n8n), the newly mounted stage's
first emit would re-capture the saved snapshot, erasing the dirty state
caused by the runner change. The save button would incorrectly go dim.
- Skip snapshot re-capture in handleDynamicFormEmit when form is already dirty
- Add mount-time emit to N8nAuthFormComponent (matching DynamicFormComponent)
- Use stable onSubmitRef to prevent useEffect subscription churn
- Add previousInitialValues guard to prevent initialValues echo loops
* style(web): align plugin list header button heights
* docs(review): update Box architecture review documents
Replace old review docs with 5 focused documents:
- box-architecture.md: deep architecture analysis (LangBot + SDK)
- box-issues.md: 22 issues rated P0/P1/P2
- box-test-coverage.md: test coverage analysis
- box-tob-analysis.md: toB commercialization analysis
- box-vs-plugin-runtime.md: Box vs Plugin runtime comparison
* feat(web): improve login error layout and add Terms of Service link
- Improve backend connection error display with bordered container,
inline icon, and better visual hierarchy
- Extract actual error message from axios response object
- Add Terms of Service link (https://langbot.app/terms) to login footer
- Add termsOfService i18n key for all 7 locales
* refactor(web): replace all hardcoded SVG icons with lucide-react
Unify icon usage across the entire frontend by replacing 67 hardcoded
SVG icons with lucide-react components across ~25 files. This improves
consistency, maintainability, and reduces bundle duplication.
Key replacements:
- Sidebar nav: Zap, LayoutDashboard, Bot, Workflow, BookMarked, etc.
- MCP forms: Loader2, XCircle, Trash2
- Monitoring: Sparkles, MessageSquare, CheckCircle2, RefreshCw, etc.
- Cards: Clock, Star, Workflow, Hexagon, Puzzle, Github, etc.
- Misc: Paperclip, AudioLines, CloudUpload, Layers, Heart, Smile
Zero hardcoded <svg> tags remain in .tsx files.
* fix(web): stop polling plugin tasks when no active installs
The PluginInstallTaskProvider was unconditionally polling
getAsyncTasks every 3s on all /home/* routes. Now it only
syncs once on mount and starts periodic polling only when
there are active (non-terminal) install tasks.
* fix(deps): update langbot-plugin version and add new dependencies
* refactor: use Space API for release checks and stop idle polling
- version.py: switch release list API from GitHub to space.langbot.app,
remove unused in-place update logic (update_all, compare_version_str),
translate all comments/logs to English
- PluginInstallTaskContext: only poll when active install tasks exist
* feat(box): add --standalone-box flag and 3-way transport decision for Box runtime
Align Box runtime connection logic with Plugin runtime's pattern:
- Docker: WebSocket to langbot_box container (ws://langbot_box:5411)
- --standalone-box: WebSocket to external Box process (ws://localhost:5411)
- Windows: subprocess + WebSocket (workaround for async stdio limitation)
- Unix/macOS: subprocess + stdio pipe (unchanged)
BoxRuntimeConnector now inherits ManagedRuntimeConnector for subprocess
lifecycle reuse. Add langbot_box service to docker-compose.yaml.
* refactor(box): use single port with path-based routing for Box WS
Update connector to use ws://host:5410/rpc/ws instead of ws://host:5411.
Update review docs to reflect the single-port architecture.
* feat(web): show Box runtime status in plugin debug info popover
Add Box status section to the debug info popover on the plugin list page,
displaying connection status, backend info, profile, active sessions,
and recent error count. Fetched from GET /api/v1/box/status in parallel
with plugin debug info. Includes i18n for all 8 supported languages.
* fix(web): remove ephemeral sandbox count from Box status display
The active_sessions count reflects transient sandbox containers that
expire after 5 minutes of inactivity, making it misleading in the UI.
Keep only connection status, backend, profile, and error count.
* feat(box): configurable sandbox scope and unified skill containers
Replace the per-message session_id with a template-based system
configurable per pipeline via 'Sandbox Scope' in the local-agent panel.
Default scope is per-chat ({launcher_type}_{launcher_id}).
Unify skill exec into the same container as default exec — skills are
mounted at /workspace/.skills/{name}/ via extra_mounts instead of
getting separate containers. All pipeline-bound skills are injected
at container creation time.
- Add box-session-id-template to pipeline metadata (select, 4 options, 8 languages)
- Add resolve_box_session_id() and build_skill_extra_mounts() to BoxService
- Rewrite native.py skill exec path to use execute_tool with shared session
- Update tests for new session_id format
- Add design doc: docs/review/box-session-scope.md
* feat(web): show active sandbox details in Box status popover
Display sandbox count and a detailed list of active sessions including
session ID, image, backend, resources (CPU/memory), network mode, and
last used time. Fetched from GET /api/v1/box/sessions in parallel.
Includes i18n for all 8 supported languages.
* feat(box): add startup and availability logging for sandbox tools
Log Box runtime initialization result (success with profile info, or
failure warning). Log native tool availability status at ToolManager
startup so it's immediately clear whether exec/read/write/edit tools
are registered for the LLM.
* feat(box): support custom sandbox container image via config.yaml
Add 'image' field to box config section. When set, it overrides the
profile default image (python:3.11-slim) for all sandbox containers.
Priority: caller-specified > config.yaml image > profile default.
* feat(box): add heartbeat and reconnection for Box runtime connector
Add 20-second heartbeat ping loop to detect silent Box runtime
disconnections. On disconnect, set available=false and attempt
reconnection after 3 seconds via the disconnect callback chain.
- BoxRuntimeConnector: heartbeat loop, disconnect callback parameter,
disconnect detection in connection callback and WS failure handler
- BoxService: wire disconnect callback to toggle available state and
re-initialize the connector on reconnection
* feat(web): move runtime status to dashboard, clean up plugin debug popover
Add SystemStatusCards component to the monitoring dashboard showing
Plugin Runtime and Box Runtime connection status with details (backend,
profile, sandbox count). Remove all Box/session status from the plugin
page debug popover — it now only shows debug URL and key.
Includes i18n for all 8 supported languages.
* refactor(web): compact system status into a single card alongside metrics
Replace the separate two-card row with a single compact 'System Status'
card placed as the 5th column in the metrics grid. Shows green/red dots
for Plugin Runtime and Box Runtime. Click to expand a popover with
connection details (backend, profile, sandbox count).
* feat: show connector error details for Plugin and Box runtime status
Record Box connector error in BoxService and expose it as
'connector_error' in GET /api/v1/box/status when unavailable.
Display error messages in the dashboard System Status popover
for both Plugin Runtime (plugin_connector_error) and Box Runtime
(connector_error) when they are disconnected.
* fix(web): auto-refresh system status and show disconnect errors in real time
Poll Plugin Runtime and Box Runtime status every 30 seconds so the
dashboard reflects disconnections without a manual page refresh.
Also re-fetch when the popover is opened for immediate feedback.
* fix(box): handle RPC failure in get_status/get_sessions gracefully
When the Box runtime disconnects, there is a race between the heartbeat
flipping _available=false and the frontend polling get_status(). If the
poll arrives first, client.get_status() throws a ConnectionClosedError
which propagated as a 500, causing the frontend to show a grey dot
(null status) instead of a red dot with error details.
Now get_status() catches RPC errors and returns available=false with
the exception message as connector_error. get_sessions() returns an
empty list when unavailable or on RPC failure.
* fix(box): add persistent reconnection loop with exponential backoff
The previous disconnect handler only retried once and then gave up.
Now spawns a background task that retries with exponential backoff
(3s, 6s, 12s, ... up to 60s) until the Box runtime is reachable again.
Uses a _reconnecting guard to prevent duplicate loops. Calls
connector.dispose() before each retry to clean up stale tasks.
* fix(box): detect disconnect when handler.run() returns normally
The generic Handler.run() catches ConnectionClosedError and breaks out
of its loop (normal return) instead of raising, because it has no
disconnect_callback. The old code only triggered reconnection in the
except branch, so a clean WebSocket close was never detected.
Now treat handler.run() returning normally (after successful handshake)
as a disconnect event, triggering the reconnection callback.
* fix(web): refresh system status card when clicking Refresh Data button
Pass a refreshKey prop through OverviewCards to SystemStatusCard that
increments on each Refresh Data click, triggering a re-fetch of Plugin
and Box runtime status alongside the monitoring data refresh.
* fix(web): fix system status card stuck in loading state
fetchStatus(showLoading=false) never called setLoading(false), so the
initial loading=true was never cleared. Simplify to always setLoading
in the finally block — the spinner only shows on the very first load
since subsequent fetches complete near-instantly.
* feat(web): show active sandbox details in dashboard Box status popover
Fetch box sessions alongside status and display each active sandbox
in the popover with session ID, image, resources (CPU/memory), and
last used time.
* feat(box): add global sandbox scope option
Add a 'Global (shared by all)' option to the sandbox scope selector.
Uses a constant '{global}' template variable that always resolves to
'global', so all users and chats share one sandbox container.
* refactor(web): replace popover with dialog for system status details
Replace the dropdown popover with a proper Dialog for runtime status
details. Add a small info button on the System Status card that opens
the dialog. Session details now show in a spacious 2-column grid layout
with full image name, backend, CPU/memory, network, mount path, and
created/last-used timestamps.
* fix(web): widen system status dialog and fix scroll border issue
Use max-w-2xl (matching other dialogs) instead of max-w-lg. Move
overflow-y-auto to an inner container with overflow-hidden on
DialogContent to prevent padding bleed at scroll edges.
* feat(web): add tooltips for truncated fields in system status dialog
Wrap session_id, image, and mount path fields with Tooltip components
so hovering over truncated text shows the full value.
* feat: add download button
* feat: successfully install
* feat: delete old filter
* feat: youhua frontend
* fix: align box runtime launch args
* feat: translate
* feat: refactor market
* feat: youhua qianduan
* chore: rename extension zh translation
* feat(extensions): unify extensions endpoint and refresh extensions page UX
- Rename /home/plugins route to /home/extensions and update all sidebar links.
- Add unified GET /api/v1/extensions returning plugins, MCP servers and skills,
sorted by name; replace the three separate frontend fetches with this single call.
- Migrate the extensions page to shadcn primitives (Tabs/Card/Alert/Badge/Skeleton/
Switch/Label) and clean up hardcoded color tokens on the extension card.
- Add a localStorage-persisted "Group by type" switch that, when enabled in the
All Types tab, renders extensions grouped by type with a compact section header.
- Show a spinner while loading and rename the empty-state copy from
"No plugins installed" to "No extensions installed".
- Rename the "格式 / Formats" filter label to "类型 / Types" across all 8 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(extensions): fallback lucide icon when extension icon is missing
Render a tinted lucide icon (Puzzle / Server / Sparkles) on the extension
card when the icon URL is empty or the image fails to load. Picked icons
distinct from EventListener (AudioWaveform) and KnowledgeEngine (Book) to
avoid visual collision with plugin component badges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sidebar): unify installed-extensions list with plugins, MCP and skills
- Render plugins, MCP servers and skills together under the "Installed
Extensions" sidebar entry, alphabetically sorted to match the list page.
- Resolve per-item routes by extension type (plugin -> /home/extensions,
mcp -> /home/mcp, skill -> /home/skills) and gate the plugin-only hover
context menu on extensionType === 'plugin'.
- Lift the "group by type" toggle into SidebarDataContext (still persisted
in localStorage) so the sidebar groups items with section headers
whenever the list page has the toggle enabled.
- Show lucide fallback icons (Server / Sparkles / Puzzle) tinted in the
LangBot blue for MCP, skill, and missing-icon plugin items, overriding
the SidebarMenuSubButton svg color rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(extensions): mobile-friendly layout for extensions and add-extension pages
- Stack the extensions page header vertically on small screens, let the
filter Tabs scroll horizontally if they overflow, hide the debug
button label below sm and let the install/debug controls wrap.
- Constrain the debug popover and its inputs to the viewport width so
they no longer overflow on phone-sized screens.
- Drop the card grid from a fixed 30rem column to a min(100%, 22rem)
column at base / 28rem at sm, and reduce the gap, so cards render
cleanly at 360px+ widths in both flat and grouped views.
- Make the add-extension header actions wrap on lg- viewports and the
install dialog responsive instead of a hard 500px box.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: change ui
* feat: delete version for mcp and skills
* fix: constrain home page content width
* fix: preserve monitoring card borders under sticky filters
* fix(box): restore sandbox config and shared mcp runtime
* fix(box): harden sandbox session isolation
* fix(skill): remove auto activation setting
* feat(skill): align skill system with Claude Code's Tool Call design
- Replace text marker activation with `activate` tool (Tool Call mechanism)
- Replace 7 authoring tools with 2: `activate` + `register_skill`
- Add builtin skills loading from templates/skills/
- Add create-skill as first builtin skill
- Remove SKILL_ACTIVATION_MARKER and text detection methods
- Tool Result returns SKILL.md content (protects KV Cache)
This aligns with Claude Code's progressive disclosure pattern:
- Metadata (name+description) always visible in tool description
- SKILL.md body loaded on activate via Tool Call
- Bundled resources accessible through virtual path mapping
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(tools): add glob and grep native sandbox tools
Add file discovery and content search capabilities to the sandbox:
- glob: Find files by pattern (supports ** recursive matching)
- grep: Search file contents with regex patterns
Both tools respect skill package paths and include safety limits
(max 100 files for glob, max 200 matches for grep).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(skill): add skill file browsing capability
- Add API endpoints for listing/reading/writing skill files
- Add FileTree component in SkillForm for directory browsing
- Users can now view scripts/, references/, assets/ directories
- Files can be selected and edited in the instructions textarea
- Add translations for new file browsing features
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(skill): copy builtin skills to data/skills on startup
- Builtin skills (templates/skills/) are now copied to data/skills/
- Users can view and manage builtin skills in the UI
- Rename SkillAuthoringToolLoader to SkillToolLoader
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(skill): improve file browsing and fix path handling
- Fix nested directory display in skill file tree (preserve root entries)
- Fix file content display when clicking files in skill browser
- Add skill manager and tool manager as proper package modules
- Separate fileContent state to allow editing non-SKILL.md files
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(toolmgr): correct skill_tool_loader attribute name
Rename skill_authoring_tool_loader to skill_tool_loader in execute_func_call
and shutdown methods to match the attribute defined in initialize().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(native): update tool descriptions to use register_skill
Replace references to removed import_skill_from_directory with
register_skill in exec/write/edit tool descriptions.
* feat(toolmgr): enhance tool initialization with backend availability checks
* refactor: remove unused imports and clean up code in various files
* feat: polish extension detail pages
* feat: persist sidebar list expansion
* fix: refine extension ui and backend errors
* fix: align add extension marketplace ui
* feat: manage skills through box runtime
* feat: support github skill installation
* fix: import github skill directories
* feat: install market extensions from card click
* feat(web): improve skill import flow
* feat: polish extension import flow
* fix(mcp): stabilize shared box managed processes
* fix(web): improve backend retry and sidebar scrolling
* docs(review): refresh box architecture review for feat/sandbox
Sync the docs/review/ suite to the current state of the feat/sandbox branch
(both LangBot and langbot-plugin-sdk), ~30 commits ahead of the prior review.
- box-architecture.md: rewrite for the new box.{backend,runtime,local,e2b}
config schema, add E2B backend, 6 native tools (incl. glob/grep), Skill
Tool Call activation, shared multi-process MCP container, SkillManager,
BoxSkillStore (SDK), 25 actions, 9 error types, heartbeat/reconnect
- box-issues.md: move resolved items (reconnect, heartbeat, Windows, nsjail
image conflict, frontend monitoring card) into a Resolved section; add
new P0 (INIT/backend ordering), P1 (extra_mounts immutability after
container creation), P2 (skill_store test gap, integration tests not in CI)
- box-session-scope.md: add §0 Implementation Status — Phase 1 shipped,
MCP unification landed earlier than originally scoped
- box-test-coverage.md: realign file inventory (4,400 -> 6,500 LOC),
add 7 new test files including SDK backend_selection/e2b/skill_store
- box-tob-analysis.md: connection recovery now满足基本要求; add E2B and
backend self-heal to capabilities; tick off Phase 1 reconnect/heartbeat
- box-vs-plugin-runtime.md: heartbeat/reconnect/Windows support now aligned
with Plugin Runtime; revise remaining gaps (WS auth, shared base class)
* refactor(box): use unified env-override mechanism for box.local config
The box module hand-rolled its own LANGBOT_BOX_LOCAL_* env parsing in two
places (connector._get_box_config and service._local_config), duplicating
logic that LoadConfigStage._apply_env_overrides_to_config already provides
generically via the SECTION__SUBSECTION__KEY convention.
- Drop the bespoke LANGBOT_BOX_LOCAL_* parsing; read box.local straight
from instance_config (the unified BOX__LOCAL__* overrides are already
applied before BoxService initializes)
- Harden _load_allowed_mount_roots to accept a comma-separated string,
since the generic mechanism stores a freshly-created key as a raw
string when config.yaml has no box.local.allowed_mount_roots entry
- docker-compose: rename the langbot container env vars to
BOX__LOCAL__* (the canonical convention); remove them entirely from
the langbot_box container — the Box runtime never reads box.local from
env/config.yaml, it is configured via the INIT RPC action
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: repair stale skill/sandbox tests for feat/sandbox
The skill subsystem moved to Tool-Call activation and a Box-managed
skill store; several tests still asserted removed APIs and a sys.modules
stub leaked across the suite. Full unit suite now green (was 23 failing).
- test_skill_tools: drop TestSkillManagerActivation (text-marker API
removed); rewrite TestSkillActivationHelper around the current
skill.activation.register_activated_skill; replace the CRUD
TestSkillAuthoringToolLoader with TestSkillToolLoader covering the
current activate/register_skill tools and sandbox-availability gating
- test_tool_manager_native: ToolManager attr is skill_tool_loader (not
skill_authoring_tool_loader); native loader now exposes 6 tools
(exec/read/write/edit/glob/grep) and requires initialize() with a
backend-available get_status()
- test_localagent_sandbox_exec: remove obsolete activation-marker
leakage tests and their helper providers
- test_model_service / pipeline conftest: give the mocks skill_mgr=None
so PreProcessor's local-agent skill-binding guard short-circuits
- test_n8nsvapi: stop permanently overwriting sys.modules
('langbot.pkg.provider.runner' etc.); save and restore around the
import so other modules get the real LocalAgentRunner base class
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(tests): run unit tests on every push to feat/** branches
- Add feat/** to push branches so long-lived feature branches are
tested on every push (they accumulate large changes before a PR)
- Drop the push path filter entirely: every push to master/develop/
feat/** now runs the full unit suite (the old 'pkg/**' filter never
matched the real source path 'src/langbot/pkg/**', so backend-only
pushes silently skipped tests)
- Fix the same broken path glob on the pull_request trigger
('pkg/**' -> 'src/langbot/pkg/**')
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skill): harden mount/reload paths and HTTP errors against stale skill cache
The Box backends behave inconsistently when extra_mounts reference a
missing host directory (nsjail aborts the entire sandbox start, Docker
silently creates a root-owned empty dir on the host, E2B silently skips
the upload). The cache in skill_mgr.skills is only refreshed on
in-process mutations, so out-of-band changes — container rebuilds,
manual rm in the box volume, anything the LangBot API didn't drive —
leave a stale skill that later produces one of those bad mount paths.
- box/service.py: build_skill_extra_mounts now filters skills whose
package_root is not isdir on the LangBot-visible filesystem and logs
a warning, instead of passing the bad mount through to the backend
- skill/manager.py: reload_skills (Box path) drops skills whose
package_root is missing on the LangBot-side filesystem before they
reach the in-memory cache, with a summary warning
- api/http/controller/groups/skills.py: file/CRUD handlers now also
catch BoxError (RuntimeError subclass, previously slipping past
``except ValueError`` and surfacing as 500); list/get handlers gain
a try/except so a transient Box RPC failure becomes a clean 400
instead of a stack trace
Tests added for build_skill_extra_mounts (skip missing, skip empty,
no skill manager) and SkillManager.reload_skills (drop missing on Box
path). Full unit suite: 279 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(box): add box.enabled toggle and gate consumers on availability
Make the Box sandbox runtime optional. When ``box.enabled`` is false in
config (or when an enabled Box fails to connect), every dependent feature
degrades to the same disabled-state UX rather than crashing or silently
falling back to less safe code paths.
Backend:
- config.yaml: new top-level ``box.enabled: true`` flag (default true)
- BoxService:
- Read box.enabled on construction
- initialize() short-circuits when disabled — no remote WS connect, no
stdio subprocess fork
- _on_runtime_disconnect is a no-op when disabled (no reconnect loop
on a deliberately-off service)
- get_status() now exposes ``enabled`` so the frontend can tell
"disabled in config" from "configured but failed"
- MCP stdio loader (mcp_stdio.uses_box_stdio): requires box_service to
be available, not just installed
- MCP _init_stdio_python_server: when ap.box_service exists but is
unavailable, refuse the stdio server with an actionable error instead
of silently falling through to host-stdio (which bypasses the sandbox
the operator asked for). Setups without ap.box_service installed at
all keep the legacy host-stdio fallback for pre-Box dev mode
- SkillService._require_box_for_write: refuses create/update/install/
write_skill_file when ap.box_service is installed but unavailable.
Distinguishes disabled vs failed in the error message so the UI can
surface the right hint. Legacy setups (no ap.box_service) keep the
local fallback path — that distinction is what keeps the existing
local-skills tests valid
Tests:
- Box disabled-state behavior (4 cases)
- Skill write refusal in disabled & failed states (7 cases)
- MCP stdio runtime info policy updated to match new refuse-when-down
behavior
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(web): surface Box disabled/unavailable state across consumers
When Box is disabled in config (``box.enabled = false``) or fails to
connect, every dependent UI surface now degrades visibly:
- ``useBoxStatus`` hook: shared, polled 30s, exposes ``available``,
``disabled`` (config-off) and a single ``hint`` key so callers don't
have to re-derive the three states
- ``BoxUnavailableNotice`` reusable Alert banner driven by that hint
- Dashboard SystemStatusCards: three-state dot + label
(connected / disabled-gray / disconnected-red); disabled state shows
the ``boxDisabled`` hint, failed state continues to show the connector
error. Plugin block kept untouched
- Skills page (create view) and SkillDetailContent (edit view):
Save button disabled and banner inserted above the form when Box is
unavailable — matches the backend gate added in the previous commit
- PipelineExtension skill section: ``enable_all_skills`` switch, Add
Skill button and Remove buttons all gate on Box availability;
banner inline under the section header
- PipelineFormComponent: banner above the ``local-agent`` stage card
when Box is unavailable, since that stage carries the sandbox-bound
``box-session-id-template`` field
- Box status payload type (``ApiRespBoxStatus.enabled``) and 8 locale
files updated with ``boxDisabled`` / ``boxUnavailable`` /
``boxRequiredHint`` strings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(box): document the box.enabled toggle and gate behavior matrix
- docker-compose: move ``langbot_box`` under compose profiles
(``box`` and ``all``) so ``docker compose up`` no longer requires
the sandbox container. Inline comment explains how to pair the
profile choice with ``box.enabled`` so the langbot service does not
thrash trying to reach a runtime that was never started
- docs/review/box-architecture.md:
- Annotate ``box.enabled`` in the config.yaml example, listing the
exact side effects (no remote/stdio connect; tools/skills/MCP
stdio off; reads still work)
- Replace the bare compose snippet with the actual profile-driven
invocation and the BOX__ENABLED pairing
- New "关闭/连接失败时的行为矩阵" section: a single table mapping
every consumer (native tools, activate/register_skill, stdio MCP,
skill list/CRUD, pipeline AI config, extensions page, dashboard)
to its disabled-state behavior, plus the legacy ``ap.box_service``
distinguisher note
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(pipeline-form): swap Box banner for field-level disable_if + tooltip
The previous commit hard-coded a BoxUnavailableNotice banner above the
``local-agent`` stage card. That works, but it shouts at the user about
every field in that stage when in reality only one field —
``box-session-id-template`` — depends on the sandbox.
Use the dynamic-form schema's existing variable-injection mechanism
(``__system.*`` references via ``systemContext``) and add a sibling to
``show_if``: ``disable_if`` + ``disabled_tooltip``. The field stays
visible, becomes inert, and an info icon next to its label exposes the
reason on hover. The rest of the AI tab is left untouched.
- entities/form/dynamic.ts: extend IDynamicFormItemSchema with
``disable_if: IShowIfCondition`` and ``disabled_tooltip: I18nObject``
- DynamicFormComponent: evaluate disable_if with the same resolver as
show_if; OR the result into isFieldDisabled; render an Info tooltip
trigger next to the label when the condition matches
- ai.yaml metadata: attach disable_if (__system.box_available eq false)
and a localized disabled_tooltip to box-session-id-template
- PipelineFormComponent: drop the BoxUnavailableNotice import and the
per-stage banner; pass ``systemContext={ box_available: boxAvailable }``
only for the local-agent stage so other stages aren't paying the
re-render cost
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): friendly UI message when stdio MCP refused by Box state
Previously the MCP detail dialog dumped the raw RuntimeError text from
``_init_stdio_python_server`` — English-only, prefixed with "Failed
after 4 attempts", and exposing internal config names. The retry
wrapper also kept retrying a refusal that is deterministically going
to fail again, polluting logs.
Replace the raw text with a structured signal:
- New ``MCPSessionErrorPhase.BOX_UNAVAILABLE`` enum value. The stdio
refusal path sets it before raising and uses a short opaque
discriminator (``box_disabled_in_config`` / ``box_unavailable``) as
the message body — never user-facing
- ``_lifecycle_loop_with_retry`` short-circuits on
``BOX_UNAVAILABLE``: surfaces the error immediately, no retries,
no "Failed after N attempts" prefix. Silences the warning storm
seen during smoke-testing
- ``MCPServerRuntimeInfo`` (TS type) now declares ``error_phase``,
``retry_count``, ``box_session_id``, ``box_enabled`` to match what
the backend already returns in get_runtime_info_dict()
- Both MCP detail forms (``mcp/components/mcp-form/MCPForm.tsx`` and
``plugins/mcp-server/mcp-form/MCPFormDialog.tsx``) detect
``error_phase === 'box_unavailable'`` and render a two-line
localized notice: state line ("Box disabled / unreachable") plus
remediation line ("enable Box or switch to http/sse")
- 8 locale files (en/zh-Hans/zh-Hant/ja/ru/vi/th/es) get
``mcp.boxDisabledStdioRefused``, ``mcp.boxUnavailableStdioRefused``,
``mcp.boxStdioRefusedSuggestion``
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp-web): block stdio MCP creation at the form when Box is unavailable
When Box is disabled in config (``box.enabled = false``) or unreachable,
saving a new MCP server in stdio mode produced one that could never
start — the user would only learn that from the runtime error on the
detail page. Stop the user before they save instead.
Both MCP forms (the page-level ``MCPForm.tsx`` and the older dialog
``MCPFormDialog.tsx``) now:
- Disable the ``stdio`` option in the mode select when Box is
unavailable, with a small "(requires Box)" suffix so the reason is
obvious. Existing stdio configs still display their current value
- Show ``BoxUnavailableNotice`` inline under the mode select when the
currently-selected mode is stdio and Box is unavailable, so editing
a stale stdio config makes the cause visible
- Disable the Save / Submit button while stdio is selected under that
condition. ``MCPForm`` exposes a new ``onSaveBlockedChange`` prop
so the parent ``MCPDetailContent`` can disable both its Submit and
Save buttons. ``MCPFormDialog`` disables its Save button locally
- Refuse the submit handler too (Enter-key path) with a toast carrying
the same i18n message
i18n: ``mcp.boxRequired`` (short tag in the disabled option) and
``mcp.stdioBlockedByBoxToast`` added to all 8 locales.
Backend runtime gate (``_init_stdio_python_server`` refusal +
``BOX_UNAVAILABLE`` error_phase + retry short-circuit) stays in place
as the last line of defence for API bypass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(web): prevent plugin config form overflow
* refactor(skill): remove all local-filesystem fallbacks; Box is the sole source
Skills now flow exclusively through the Box runtime. Every read and write
method funnels through ``_box_service()``; when Box is unavailable
(disabled in config, connection failed, or simply not installed) the
operation either returns an empty surface (``list_skills`` → []) or
raises with a clear ``Box runtime ... not initialised / disabled /
unavailable: ...`` message via the new ``_require_box(action)`` helper.
Why: the legacy local-fallback path scanned ``data/skills/``, but Box
manages its own ``box.local.skills_root`` (default ``data/box/skills/``).
The two diverging directories caused stale / phantom skill lists when
Box flapped, and the local-fallback writes silently bypassed all the
sandboxing the operator had configured.
SkillService (``api/http/service/skill.py``):
- New ``_require_box(action)`` returns the box service or raises a
structured ValueError. ``_require_box_for_write`` kept as alias
- ``list_skills`` → returns [] when Box is down so the UI can render
the disabled banner cleanly
- ``get_skill`` / ``get_skill_by_name`` → return None
- All read-file / write-file / scan-dir / create / update / delete /
install / preview methods → ``_require_box`` then box delegate.
Local fallback bodies (shutil.copytree, tempfile.mkdtemp, preview
pipelines) removed entirely
SkillManager (``pkg/skill/manager.py``):
- ``reload_skills`` returns early with empty cache when Box is down.
data/skills/ discovery loop removed
- ``refresh_skill_from_disk`` now just reports cache presence; the
on-disk re-parse is gone since Box is the only writer
Tests:
- Drop 11 obsolete test_skill_service.py tests that exercised the
removed local-fallback paths (create/install/file/delete/update)
- Add list-empty + read-refused tests; flip the legacy-allow test to
legacy-refuses-too
- Rewrite refresh_skill_from_disk test to match the new behaviour
Several helper methods (_managed_skill_path, _resolve_skill_path,
_preview_skill_candidates, _install_preview_candidates, etc.) are now
unreachable; a follow-up commit will prune them so this diff stays
reviewable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(skill): prune dead local-filesystem helpers left over from Box migration
Follow-up to the Box-only refactor. The previous commit removed the
local-fallback BRANCHES from every public method; this one removes the
HELPERS those branches called, which are now unreachable.
SkillService (service/skill.py): 787 → 449 lines
Removed: scan_directory (sync), _read_skill_package, _write_skill_md,
_resolve_create_field, _managed_skill_path,
_managed_install_root_for_package, _normalize_package_root,
_resolve_skill_path, _find_skill_entry, _discover_skill_directories,
_safe_extract_zip, _extract_uploaded_skill_to_temp,
_download_github_skill_to_temp, _resolve_github_source_root,
_build_preview_target_dir, _preview_skill_candidates,
_select_preview_candidates, _install_preview_candidates,
_preview_source_root, _resolve_installed_skills, plus the
module-level _FRONTMATTER_FIELDS and _build_skill_md.
Kept (still needed by the surviving GitHub-import path):
_download_github_asset, _download_github_skill_directory_as_zip,
_find_github_skill_archive_entry, _copy_github_skill_directory_to_zip,
_is_github_skill_md_url, _parse_github_skill_md_url,
_resolve_github_skill_md_package_name, _validate_github_asset_url,
_uploaded_skill_target_stem, _validate_skill_name.
Imports dropped: shutil, tempfile, yaml, ....utils.paths.
SkillManager (skill/manager.py): 187 → 88 lines
Removed: get_managed_skills_root, _discover_skill_directories,
_find_skill_entry, _load_skill_file, _normalize_package_root.
Imports dropped: datetime, parse_frontmatter, paths.
Tests:
- test_skill_service.py: drop the 3 sync scan_directory tests +
skill_service fixture + _create_skill_file helper
- test_skill_tools.py: drop test_load_skill_file_success; rename
TestSkillManagerPackageLoading → TestSkillManagerCache
Full unit suite: 277 passed, 1 skipped. ``ruff check`` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skill): re-inject skill index into local-agent system prompt
The contributor's original PR (#1917) appended an ``Available Skills``
index to the system prompt before the LLM saw the user message, so the
LLM could decide whether to activate a skill. ``7145447b`` removed the
text-marker activation flow and, together with it, the entire system
prompt injection — but the Tool Call replacement only put the available
skills inside the ``activate`` tool's description. In practice the LLM
ignores tool descriptions for selection and goes straight to native
tools, so user-visible skill activation silently broke.
Restore the injection, adapted for the Tool Call era:
- SkillManager regains ``get_skill_index(bound_skills)`` and
``build_skill_aware_prompt_addition(bound_skills)``. The addendum
carries only ``name (display_name): description`` for each
pipeline-visible skill plus one instruction line pointing at the
``activate`` tool. No SKILL.md contents — KV cache stays clean
- PreProcessor appends the addendum to the first system message (or
inserts a new one) of ``query.prompt.messages`` for the local-agent
runner. Handles plain-string and ContentElement[] bodies. Skips
cleanly when no skills are visible
- 3 new test_preproc cases: injection happens, bound-skills subset
honoured, empty addendum touches nothing. 280 passed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(box): downgrade get_status.available when backend probed unavailable
Until now ``BoxService.get_status`` returned ``available: true`` whenever
the runtime connector was healthy, even if the runtime itself reported
``backend: { available: false }`` (operator selected nsjail without the
binary, Docker daemon crashed mid-session, E2B credentials wrong, ...).
The dashboard / ``useBoxStatus`` hook / skill_service gate consumed the
top-level flag and showed "connected" while every actual call to native
exec or skill management would fail.
The native-tool loader already polled ``status.backend.available``
independently and hid its tools correctly, but every other consumer
(dashboard banner, the disabled-state hint, the LLM-facing message)
disagreed with it.
Combine the two in the payload: ``available = self._available AND
status.backend.available``. When ``backend.available`` is false we now
also surface a ``connector_error`` that names the backend
("Configured sandbox backend \"nsjail\" is unavailable") so the dialog
shows the actionable reason instead of an empty error pane. The
detailed ``backend`` object is preserved unchanged for the dialog.
Internal ``box_service.available`` (used by ``skill_service`` writes,
``mcp_stdio.uses_box_stdio``, the reconnect callback) is intentionally
NOT changed — it still tracks connector health only, so a backend blip
does not trigger spurious reconnect loops.
Tests:
- ``test_get_status_downgrades_available_when_backend_dead`` — exercise
the new branch (connector OK, backend.available=false → top-level
available=false, connector_error mentions the backend name)
- ``test_get_status_keeps_available_true_when_backend_ok`` — guard
against regressing the happy path
Live-verified with ``box.backend: nsjail`` on macOS (no nsjail binary):
``GET /api/v1/box/status`` now returns ``available: false`` with the
named connector_error, instead of the previous misleading
``available: true``.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(web): surface the specific Box failure reason in unavailable banner
When Box is configured but the runtime reports its backend is dead
(e.g. ``box.backend = nsjail`` but the binary is missing, or Docker
daemon crashed), the backend now returns a structured
``connector_error`` like ``Configured sandbox backend "nsjail" is
unavailable``. The previous notice only said "Box sandbox is
unavailable" + a generic "enable Box" hint, hiding the actionable
detail.
- ``useBoxStatus``: derive ``reason`` from ``status.connector_error``.
Only exposed for the failed-state (``hint === 'boxUnavailable'``),
since the disabled-by-config message already carries its reason
- ``BoxUnavailableNotice``: insert the reason as a small monospaced
line between the state message and the action hint. The disabled
variant is unchanged (operator chose the state)
- Wire ``reason`` through every existing call site (Skills page +
detail, PipelineExtension, both MCP forms). Old unused ``context``
prop dropped
Net layout (3 lines, still compact):
⚠ Box sandbox is unavailable — sandbox tools, skill add/edit, ...
Configured sandbox backend "nsjail" is unavailable
This feature requires the Box runtime. Enable it in config ...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: reconcile master's unit tests with feat/sandbox refactors
The merge from master brought in new unit tests that target pre-refactor
APIs on feat/sandbox. Reconcile each:
- factories/app.py: FakeApp now exposes a Mock skill_mgr (with empty .skills
dict + inert prompt-addition builder) and a Mock pipeline_service so the
PreProcessor skill-index injection branch can run end-to-end in tests.
- pipeline/conftest.py: eagerly import langbot.pkg.pipeline.pipelinemgr so
pipeline.stage is fully initialised before any individual stage test
(preproc, longtext, ...) tries to lazy-load it. Without this preload,
running test_preproc.py in isolation hit a circular-import error via the
stage -> app -> pipelinemgr -> stage chain.
- provider/test_tool_manager.py: ToolManager now probes four loaders
(native -> plugin -> mcp -> skill). Inject inert native + skill mocks in
the execute_func_call fixture and assert all four shutdowns fire.
- utils/test_paths.py: drop the three cwd-dependent _check_if_source_install
cases. The refactor walks Path(__file__).resolve().parents looking for
pyproject.toml + main.py, so cwd no longer factors in and there's no
file read to mock-fail. The positive case and caching test still apply.
- utils/test_version.py: delete entirely. is_newer and compare_version_str
were removed when VersionManager was refactored to use the Space API for
release checks (1b4107a9); the tests targeted a surface that no longer
exists.
* refactor(box): launch box runtime via the lbp CLI subcommand
Mirror the plugin runtime: box is now started through the same CLI entry
point (langbot_plugin.cli) instead of the box module directly.
- docker-compose.yaml: langbot_box command runs `langbot_plugin.cli ... box`
(WebSocket is the default transport, no flag needed — matches `rt`).
- box/connector.py: both subprocess launch sites (_start_local_stdio and
the Windows _start_subprocess_then_ws path) invoke
`langbot_plugin.cli.__init__ box`, using `-s` for the stdio transport.
- docs/review: update stale `-m langbot_plugin.box[.server]` references.
Pairs with the SDK change that removes box's direct-launch entry points
(python -m langbot_plugin.box / .box.server) and the legacy --mode flag.
* chore: bump langbot-plugin beta 1
* fix(ci): resolve langbot-plugin from PyPI and clear lint failures
CI on feat/sandbox failed across Unit Tests, Lint and Build Dev Image.
Root causes and fixes:
- pyproject.toml had a [tool.uv.sources] editable override pinning
langbot-plugin to ../langbot-plugin-sdk. That path only exists in a
paired local checkout, so `uv sync` failed on every CI runner
("Distribution not found"). Remove the override and regenerate uv.lock
so langbot-plugin==0.4.0b1 resolves from PyPI, matching master.
- tests/integration/api/test_pipelines.py: the pipeline extensions
endpoint now calls ap.skill_service.list_skills(); add the missing
skill_service mock to the fake_pipeline_app fixture (the test came
from master, the endpoint change from feat/sandbox).
- Apply ruff format to three src files and prettier to three web files
that had committed formatting drift, failing `ruff format --check`
and `pnpm lint`.
* chore: bump beta version
* docs: remove BOX_BACKEND override reference
* fix(pipelines): stop attributing dashboard debug WS to bound web_page_bot
The dashboard pipeline-debug WebSocket
(/api/v1/pipelines/<uuid>/ws/connect) and the embed widget WebSocket
(/api/v1/embed/<bot_uuid>/ws/connect) already live on separate paths,
but the debug handler ran `_find_owner_bot(pipeline_uuid)` and, when
the same pipeline happened to be bound to a web_page_bot, passed that
bot as `owner_bot` into `handle_websocket_message`. The adapter then
used the page bot's listeners + adapter for the request, so debug
sessions were logged as "page bot" activity in the dashboard.
Debug sessions must always run under the built-in websocket_proxy_bot.
Remove `_find_owner_bot`, drop the `owner_bot` parameter from the
debug-path `_handle_receive`, and call `handle_websocket_message`
without it so the adapter takes its default proxy-bot branch. The
embed handler still resolves and passes its `runtime_bot` for the
page-bot path, so attribution there is unchanged.
* fix(plugin): install marketplace MCP from canonical mode + extra_args
_install_mcp_from_marketplace read the dropped `mcp_data.config` field
and reconstructed mode/extra_args by guessing from the URL — which lost
stdio's command/args/env/box entirely, so stdio MCP installs from the
marketplace always failed.
Use the Space record's canonical `mode` and `extra_args` directly (the
same shape stored in mcp_servers), and gate the install on `mode`
instead of the removed `config`. After a successful install, best-effort
POST to the marketplace install endpoint to bump install_count.
* feat(web): show recommendation lists in plugin market; mixed-type icons
The marketplace recommendation lists (curated rows from Space) were never
mounted in the plugin market page. Wire them in:
- fetch recommendation lists on mount and render them above the extension
grid, only when no search/filter is active.
Recommendation lists now mix plugins, MCPs and skills, so resolve each
card's icon by type (plugin / mcp / skill marketplace icon URL) instead of
always using the plugin icon endpoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): auto-open install dialog from one-click deep link
Accept a deep link from LangBot Space's one-click install:
/home/add-extension?install=1&extension_type=<plugin|mcp|skill>&author=&name=&version=
On mount, populate the install info, open the confirm dialog directly, and
strip the params from the URL. Reuses the existing marketplace install flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: push marketplace URL to runtime; fix market client base race
- On connecting to the plugin runtime, push the configured space.url via the
new SET_RUNTIME_CONFIG action so the runtime downloads plugins from the same
Space, instead of relying on its own CLOUD_SERVICE_URL env/default. Wrapped
in try/except so an older SDK without the action degrades gracefully.
- web: the plugin market fetched recommendation lists (and listings) via the
sync cloud client before its baseURL was resolved from system info, so it
hit the default space.langbot.app. Await getCloudServiceClient() before the
initial fetches and for the recommendation list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): don't show MCP "connection failed" while still connecting
The MCP status UI rendered "连接失败" for any non-connected state, so during a
normal connection attempt the subtitle showed "连接失败" while the status pill
below it showed "连接中..." — contradictory.
Only treat an explicit ERROR (or box-unavailable) status as failed; a
CONNECTING or initial/unresolved status now shows "连接中". Applied to the MCP
detail form (subtitle + StatusDisplay) and the MCP server card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): type-aware install dialog + refresh sidebar after install
The marketplace install confirm dialog was hardcoded to "安装插件 / 确定要安装
插件 X 吗" for every type. Make it type-aware (plugin / MCP / skill) and show
more info: type chip, author/name id, and version when present.
Also refresh all sidebar extension lists (plugins, MCP servers, skills) when
an install task completes, so the newly-installed extension appears
immediately regardless of type (previously only refreshPlugins ran).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): richer install dialog (icon + name + description), drop redundant type row
The install dialog already states the type in its title, so the "类型" row was
redundant. Replace the info box with the extension's icon (avatar), display
name, author/name id + version, and description — built from the PluginV4 for
in-app installs and from the icon endpoint by type for the one-click deep link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): TDZ crash in add-extension (installIconURL before installInfo)
installIconURL was computed above the useState declaration of installInfo,
causing "Cannot access 'installInfo' before initialization" (500) on the
add-extension page. Move the computation below the state declarations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): redesign install-progress dialog for MCP/skill
The progress dialog showed plugin-only stages (download + dependency install)
for every type. MCP/skill have no such steps, so show a single
"installing → done/failed" row for them (MCP: adding & connecting the server;
skill: installing the package) while keeping the detailed download/deps
stages for plugins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): add missing market.componentName i18n keys
The marketplace component filter (and component badges) used
market.componentName.{Tool,Command,EventListener,KnowledgeEngine,Parser,Page}
but those keys only existed under plugins.componentName, so the market UI
showed raw keys. Add a componentName block to the market namespace (zh-Hans +
en-US; other locales fall back to zh-Hans).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): sidebar extensions refresh button + full-name tooltip
- Add a refresh button to the installed-extensions category header in the
sidebar; it re-fetches plugins + MCP servers + skills and spins while
loading.
- The sidebar item tooltip now shows the extension's full name (with the
description below when present), so truncated MCP/extension names are
readable on hover.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugin-market): rename component filter to "插件组件" with hint tooltip + persist filters
- Rename the in-app plugin market component filter label to "插件组件" /
"Plugin Component"
- Add an Info icon tooltip explaining what plugin components are (Tool /
Command / EventListener, etc.)
- Persist filter selections (type / component / tags / sort) in localStorage
so they survive reloads; restored on mount (URL type param still wins)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(plugin-market): restore missing "页面"(Page) component filter option
The market component-filter list on this branch was a diverged rewrite that
dropped the Page component kind master had added. The i18n key
(market.componentName.Page) already existed; re-add the Page entry to the
componentOptions list so plugins providing Page components can be filtered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(i18n): reword plugin component filter hint
Drop the redundant "插件组件是" lead-in and mention that components extend
LangBot's capabilities; mirror the wording in en-US.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(i18n): backfill missing market/addExtension keys in 6 locales
check-i18n surfaced that market.componentName.*, market.filterByComponentHint
and the addExtension.install* keys existed only in en-US/zh-Hans. Backfill
them for es-ES, ja-JP, ru-RU, th-TH, vi-VN and zh-Hant (reusing each locale's
existing component-name translations) and align the filterByComponent label
with the new "Plugin Component" wording. check-i18n now passes for all locales.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* i18n(plugins): relabel "group by type" as "group by format"
The installed-extensions grouping is by extension format (plugin / MCP / skill),
so rename the toggle label accordingly across all 8 locales (key unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(plugin-market): cursor-pointer on tag filter trigger
The TagsFilter Select trigger used the default cursor; add cursor-pointer so the
tag filter is clearly clickable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sidebar): show edition badge (Community / Cloud) in logo area
Add a small badge next to the LangBot name in the sidebar header that reflects
systemInfo.edition: a neutral "Community" badge for the community edition and a
blue "Cloud" badge for the cloud edition. Adds sidebar.editionCommunity /
sidebar.editionCloud across all 8 locales.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* i18n(sidebar): unify zh-Hans cloud edition label to 云端版
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sidebar): edition badge - drop hover, use "Cloud" in all locales
The edition badge is not interactive, so remove the hover background on the
cloud badge. Also use the literal "Cloud" label uniformly across all locales
instead of localized variants (云端版/クラウド版/...).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(box): cap tool-call loop and run workspace-quota walk off the event loop
Two robustness fixes that bite under normal sandbox usage (not just attack),
hardening the self-hosted community edition before release:
- localagent: cap the tool-call loop at MAX_TOOL_CALL_ROUNDS (128). A looping
or adversarial model could otherwise emit tool calls indefinitely (each
potentially a sandbox exec), producing a non-terminating request and runaway
cost. The cap is generous enough not to interrupt legitimate multi-step
agentic workflows.
- box.service: make _enforce_workspace_quota async and run the recursive
workspace scan via asyncio.to_thread. It ran on every quota-enforced exec and
a large workspace would block the whole asyncio runtime (all bots/pipelines).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(review): refresh box docs; trim issue list to SaaS blockers only
Community self-hosted edition is release-ready, so the box review docs are
updated to current state (date 2026-06-02 + status note) and box-issues.md is
rewritten to keep only the SaaS / multi-tenant / network-exposed release
blockers (S1-S8): unauthenticated control plane, no per-pipeline exec
authorization, unbounded sessions + no reaper, no kernel-level quota, mount
validation gaps (/ + extra_mounts), missing container hardening, lock-around-
cold-start, and the lower-severity follow-ups. Resolved items (tool-call loop
cap, async quota scan, host_path mount allowlist, _is_path_under dedup) moved to
a short "resolved before community release" record; community-only and
pure-cleanup items dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(deps): pin langbot-plugin to 0.4.0
Track the stable SDK release (0.4.0b1 -> 0.4.0); regenerate uv.lock.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: WangCham <651122857@qq.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: fdc310 <82008029+fdc310@users.noreply.github.com>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
1569 lines
61 KiB
TypeScript
1569 lines
61 KiB
TypeScript
const zhHant = {
|
||
sidebar: {
|
||
home: '首頁',
|
||
extensions: '擴展',
|
||
installedPlugins: '已安裝外掛',
|
||
pluginMarket: '外掛市場',
|
||
mcpServers: 'MCP 伺服器',
|
||
addExtension: '添加擴展',
|
||
pluginPages: '插件頁面',
|
||
pluginPagesTooltip: '由已安裝的插件提供的視覺化頁面',
|
||
quickStart: '快速開始',
|
||
scrollToBottom: '捲動到底部',
|
||
editionCommunity: '社區版',
|
||
editionCloud: 'Cloud',
|
||
},
|
||
common: {
|
||
login: '登入',
|
||
logout: '登出',
|
||
accountOptions: '系統設定',
|
||
account: '帳戶',
|
||
integration: '連接',
|
||
email: '電子郵件',
|
||
password: '密碼',
|
||
welcome: '歡迎回到 LangBot 👋',
|
||
continueToLogin: '登入以繼續',
|
||
loginSuccess: '登入成功',
|
||
loginFailed: '登入失敗,請檢查電子郵件和密碼是否正確',
|
||
loginLoadError: '無法連線到伺服器',
|
||
loginLoadErrorDesc: '無法連線到 LangBot 後端服務,請確認服務已啟動後重試。',
|
||
retry: '重試',
|
||
enterEmail: '輸入電子郵件地址',
|
||
enterPassword: '輸入密碼',
|
||
invalidEmail: '請輸入有效的電子郵件地址',
|
||
emptyPassword: '請輸入密碼',
|
||
language: '語言',
|
||
helpDocs: '輔助說明',
|
||
featureRequest: '需求建議',
|
||
starOnGitHub: '在 GitHub 上 Star',
|
||
create: '建立',
|
||
edit: '編輯',
|
||
delete: '刪除',
|
||
add: '新增',
|
||
select: '請選擇',
|
||
skill: '技能',
|
||
cancel: '取消',
|
||
submit: '提交',
|
||
error: '錯誤',
|
||
success: '成功',
|
||
save: '儲存',
|
||
saving: '儲存中...',
|
||
recommend: '推薦',
|
||
start: '開始',
|
||
confirm: '確認',
|
||
confirmDelete: '確認刪除',
|
||
deleteConfirmation: '您確定要刪除這個嗎?',
|
||
selectOption: '選擇一個選項',
|
||
selectPreset: '選擇預設',
|
||
required: '必填',
|
||
enable: '是否啟用',
|
||
name: '名稱',
|
||
description: '描述',
|
||
icon: '圖標',
|
||
close: '關閉',
|
||
deleteSuccess: '刪除成功',
|
||
deleteError: '刪除失敗:',
|
||
addRound: '新增回合',
|
||
copy: '複製',
|
||
copySuccess: '複製成功',
|
||
copyFailed: '複製失敗',
|
||
test: '測試',
|
||
forgotPassword: '忘記密碼?',
|
||
agreementNotice: '繼續即表示您同意我們的',
|
||
termsOfService: '服務條款',
|
||
privacyPolicy: '隱私政策',
|
||
and: '和',
|
||
dataCollectionPolicy: '數據收集政策',
|
||
dataCollectionPolicyUrl: 'https://link.langbot.app/zh/docs/data-policy',
|
||
loading: '載入中...',
|
||
fieldRequired: '此欄位為必填',
|
||
or: '或',
|
||
loginWithSpace: '透過 Space 登入',
|
||
spaceLoginRecommended: '推薦:使用官方提供的穩定模型 API 和雲服務',
|
||
loginLocal: '使用本地帳號登入',
|
||
loginWithPassword: '透過密碼登入',
|
||
spaceLoginTitle: '透過 Space 登入',
|
||
spaceLoginDescription: '掃描二維碼或訪問下方連結進行授權',
|
||
spaceLoginUserCode: '您的驗證碼',
|
||
spaceLoginExpires: '驗證碼將在 {{seconds}} 秒後過期',
|
||
spaceLoginWaiting: '等待授權中...',
|
||
spaceLoginSuccess: '授權成功',
|
||
spaceLoginFailed: 'Space 登入失敗',
|
||
spaceLoginExpired: '驗證碼已過期,請重試',
|
||
spaceLoginCancel: '取消',
|
||
spaceLoginVisitLink: '訪問連結',
|
||
spaceLoginProcessing: '正在透過 Space 登入',
|
||
spaceLoginProcessingDescription: '請稍候,正在完成登入...',
|
||
spaceLoginSuccessDescription: '正在跳轉到 LangBot...',
|
||
spaceLoginError: '登入失敗',
|
||
spaceLoginNoCode: '缺少授權碼',
|
||
backToLogin: '返回登入',
|
||
backToHome: '返回首頁',
|
||
spaceAccountCannotChangePassword: 'Space 帳戶無法在此修改密碼',
|
||
theme: '主題',
|
||
changePassword: '修改密碼',
|
||
currentPassword: '當前密碼',
|
||
newPassword: '新密碼',
|
||
confirmNewPassword: '確認新密碼',
|
||
enterCurrentPassword: '輸入當前密碼',
|
||
enterNewPassword: '輸入新密碼',
|
||
enterConfirmPassword: '確認新密碼',
|
||
currentPasswordRequired: '當前密碼不能為空',
|
||
newPasswordRequired: '新密碼不能為空',
|
||
confirmPasswordRequired: '確認密碼不能為空',
|
||
passwordsDoNotMatch: '兩次輸入的密碼不一致',
|
||
changePasswordSuccess: '密碼修改成功',
|
||
changePasswordFailed: '密碼修改失敗,請檢查當前密碼是否正確',
|
||
apiIntegration: 'API 整合',
|
||
apiKeys: 'API 金鑰',
|
||
manageApiIntegration: '管理 API 整合',
|
||
manageApiKeys: '管理 API 金鑰',
|
||
createApiKey: '建立 API 金鑰',
|
||
apiKeyName: 'API 金鑰名稱',
|
||
apiKeyDescription: 'API 金鑰描述',
|
||
apiKeyValue: 'API 金鑰值',
|
||
apiKeyCreated: 'API 金鑰建立成功',
|
||
apiKeyDeleted: 'API 金鑰刪除成功',
|
||
apiKeyDeleteConfirm: '確定要刪除此 API 金鑰嗎?',
|
||
apiKeyNameRequired: 'API 金鑰名稱不能為空',
|
||
copyApiKey: '複製 API 金鑰',
|
||
apiKeyCopied: 'API 金鑰已複製到剪貼簿',
|
||
noApiKeys: '暫無 API 金鑰',
|
||
apiKeyHint: 'API 金鑰允許外部系統訪問 LangBot 的 Service API',
|
||
webhooks: 'Webhooks',
|
||
createWebhook: '建立 Webhook',
|
||
webhookName: 'Webhook 名稱',
|
||
webhookUrl: 'Webhook URL',
|
||
webhookDescription: 'Webhook 描述',
|
||
webhookEnabled: '是否啟用',
|
||
webhookCreated: 'Webhook 建立成功',
|
||
webhookDeleted: 'Webhook 刪除成功',
|
||
webhookDeleteConfirm: '確定要刪除此 Webhook 嗎?',
|
||
webhookNameRequired: 'Webhook 名稱不能為空',
|
||
webhookUrlRequired: 'Webhook URL 不能為空',
|
||
noWebhooks: '暫無 Webhook',
|
||
webhookHint: 'Webhook 允許 LangBot 將個人訊息和群組訊息事件推送到外部系統',
|
||
actions: '操作',
|
||
apiKeyCreatedMessage: '請複製此 API 金鑰,若按鈕無效,請手動複製。',
|
||
none: '無',
|
||
more: '更多 ({{count}})',
|
||
less: '收起',
|
||
noItems: '暫無內容',
|
||
},
|
||
notFound: {
|
||
title: '頁面不存在',
|
||
description:
|
||
'您要查詢的頁面似乎不存在。請檢查您輸入的 URL 是否正確,或返回首頁。',
|
||
back: '上一級',
|
||
home: '返回主頁',
|
||
help: '查看說明文件',
|
||
},
|
||
models: {
|
||
title: '模型設定',
|
||
description: '設定和管理可在流程線中使用的模型',
|
||
createModel: '建立模型',
|
||
editModel: '編輯模型',
|
||
getModelListError: '取得模型清單失敗:',
|
||
modelName: '模型名稱',
|
||
modelProvider: '模型供應商',
|
||
modelBaseURL: '基礎 URL',
|
||
modelAbilities: '模型能力',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗:',
|
||
createSuccess: '建立成功',
|
||
createError: '建立失敗:',
|
||
deleteSuccess: '刪除成功',
|
||
deleteError: '刪除失敗:',
|
||
deleteConfirmation: '您確定要刪除這個模型嗎?',
|
||
modelNameRequired: '模型名稱不能為空',
|
||
modelProviderRequired: '模型供應商不能為空',
|
||
requestURLRequired: '請求URL不能為空',
|
||
apiKeyRequired: 'API Key不能為空',
|
||
keyNameRequired: '鍵名不能為空',
|
||
mustBeValidNumber: '必須是有效的數字',
|
||
mustBeTrueOrFalse: '必須是 true 或 false',
|
||
requestURL: '請求URL',
|
||
scanURL: '掃描模型 URL',
|
||
scanURLPlaceholder: '留空則使用請求 URL + /models',
|
||
scanURLDescription:
|
||
'當模型掃描與模型調用不使用相同地址時,請填寫實際的模型列表端點。',
|
||
apiKey: 'API Key',
|
||
abilities: '能力',
|
||
selectModelAbilities: '選擇模型能力',
|
||
visionAbility: '視覺能力',
|
||
functionCallAbility: '函數呼叫',
|
||
extraParameters: '額外參數',
|
||
addParameter: '新增參數',
|
||
keyName: '鍵名',
|
||
type: '類型',
|
||
value: '值',
|
||
string: '字串',
|
||
number: '數字',
|
||
boolean: '布林值',
|
||
object: '物件',
|
||
objectJsonPlaceholder: '{ "type": "disabled" }',
|
||
invalidJsonObject: '值必須是有效的 JSON 物件',
|
||
selectModelProvider: '選擇模型供應商',
|
||
modelProviderDescription: '請填寫供應商向您提供的模型名稱',
|
||
modelManufacturer: '模型廠商',
|
||
aggregationPlatform: '中轉平台',
|
||
selfDeployed: '自部署',
|
||
builtin: '內建',
|
||
selectModel: '請選擇模型',
|
||
testSuccess: '測試成功',
|
||
testError: '測試失敗,請檢查模型設定',
|
||
llmModels: '對話模型',
|
||
localProvider: '本地',
|
||
localProviderDescription: '在本地設定和管理的模型',
|
||
spaceProviderDescription: '從您的 Space 帳戶同步的模型',
|
||
spaceDisabledForLocalAccount: '使用 Space 登入以使用雲端模型',
|
||
syncModels: '同步',
|
||
syncSuccess: '同步完成:建立 {{created}} 個,更新 {{updated}} 個',
|
||
syncError: '同步失敗:',
|
||
spaceModelReadOnly: 'Space 模型為唯讀',
|
||
noSpaceModels: '暫無 Space 模型。點擊同步按鈕從 Space 取得模型。',
|
||
noLocalModels: '暫無本地模型。點擊建立按鈕新增模型。',
|
||
providerCount: '共 {{count}} 個供應商',
|
||
addModel: '新增模型',
|
||
manualAdd: '手動添加',
|
||
scanAdd: '掃描添加',
|
||
scanModels: '掃描模型',
|
||
scanModelsHint: '從目前供應商介面讀取可用模型,然後勾選要添加的模型。',
|
||
scannedModels: '掃描結果',
|
||
scanDebug: '調試資訊',
|
||
searchScannedModels: '搜尋掃描結果',
|
||
noScannedModels: '尚無掃描結果,點擊上方按鈕開始掃描。',
|
||
noScannedModelsMatch: '沒有符合的模型',
|
||
addSelectedModels: '添加所選模型',
|
||
addSelectedModelsSuccess: '已添加 {{count}} 個模型',
|
||
selectAll: '全選模型',
|
||
alreadyAdded: '已添加',
|
||
addLLMModel: '新增對話模型',
|
||
addEmbeddingModel: '新增嵌入模型',
|
||
provider: '供應商',
|
||
existingProvider: '現有供應商',
|
||
newProvider: '新供應商',
|
||
selectProvider: '選擇供應商',
|
||
requester: '供應商類型',
|
||
selectRequester: '選擇供應商類型',
|
||
langbotModelsDescription: '由 LangBot Space 提供的雲端模型',
|
||
credits: '積分',
|
||
loginWithSpace: '使用 Space 登入',
|
||
loginToUseModels: '使用 Space 登入以使用雲端模型',
|
||
noModels: '暫無模型',
|
||
langbotModels: 'LangBot 模型',
|
||
spaceTrialTooltip:
|
||
'免費試用積分已就緒!使用 Space 登入即可零設定使用雲端模型。',
|
||
unlockModels: '登入以使用',
|
||
editProvider: '編輯供應商',
|
||
addProvider: '新增供應商',
|
||
addProviderHint: '新增供應商以使用其他來源的模型',
|
||
addProviderHintSimple: '新增供應商以使用模型',
|
||
noProviders: '暫無供應商',
|
||
providerName: '供應商名稱',
|
||
providerNameRequired: '供應商名稱不能為空',
|
||
requesterRequired: '供應商類型不能為空',
|
||
providerSaved: '供應商已儲存',
|
||
providerCreated: '供應商已建立',
|
||
providerSaveError: '儲存供應商失敗:',
|
||
providerDeleted: '供應商已刪除',
|
||
providerDeleteError: '刪除供應商失敗:',
|
||
deleteProviderConfirmation: '您確定要刪除這個供應商嗎?',
|
||
loadError: '載入資料失敗',
|
||
chat: '對話',
|
||
embedding: '嵌入',
|
||
rerank: '重排序',
|
||
rerankUrlTooltip:
|
||
'完整 URL 覆蓋重排序端點(例如:https://dashscope.aliyuncs.com/compatible-api/v1/reranks)',
|
||
rerankPathTooltip:
|
||
'附加到基礎 URL 的路徑(預設:rerank,某些服務使用 reranks)',
|
||
modelsCount: '{{count}} 個模型',
|
||
expandModels: '展開',
|
||
collapseModels: '收起',
|
||
fallback: {
|
||
primary: '主模型',
|
||
fallbackList: '備用模型',
|
||
addFallback: '新增備用模型',
|
||
},
|
||
},
|
||
bots: {
|
||
title: '機器人',
|
||
description: '建立和管理機器人,這是 LangBot 與各個平台連接的入口',
|
||
createBot: '建立機器人',
|
||
selectFromSidebar: '從側邊欄選擇一個機器人',
|
||
editBot: '編輯機器人',
|
||
getBotListError: '取得機器人清單失敗:',
|
||
botName: '機器人名稱',
|
||
botDescription: '機器人描述',
|
||
botNameRequired: '機器人名稱不能為空',
|
||
botDescriptionRequired: '機器人描述不能為空',
|
||
adapterRequired: '適配器不能為空',
|
||
defaultDescription: '一個機器人',
|
||
getBotConfigError: '取得機器人設定失敗:',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗:',
|
||
createSuccess: '建立成功 請啟用或修改綁定流程線',
|
||
createError: '建立失敗:',
|
||
deleteSuccess: '刪除成功',
|
||
deleteError: '刪除失敗:',
|
||
deleteConfirmation: '您確定要刪除這個機器人嗎?',
|
||
platformAdapter: '平台/適配器選擇',
|
||
selectAdapter: '選擇適配器',
|
||
adapterConfig: '適配器設定',
|
||
viewAdapterDocs: '查看文檔',
|
||
bindPipeline: '綁定流程線',
|
||
selectPipeline: '選擇流程線',
|
||
selectBot: '請選擇機器人',
|
||
botLogTitle: '機器人日誌',
|
||
enableAutoRefresh: '開啟自動重新整理',
|
||
session: '對話',
|
||
yesterday: '昨天',
|
||
earlier: '更久之前',
|
||
dateFormat: '{{month}}月{{day}}日',
|
||
setBotEnableError: '設定機器人啟用狀態失敗',
|
||
log: '日誌',
|
||
configuration: '設定',
|
||
logs: '日誌',
|
||
basicInfo: '基礎資訊',
|
||
basicInfoDescription: '設定機器人名稱和描述',
|
||
routingConnection: '路由與連接',
|
||
routingConnectionDescription: '綁定處理此機器人訊息的流程線',
|
||
routingRules: '條件路由規則',
|
||
routingRulesDescription:
|
||
'按順序匹配,命中第一條規則後路由到對應流程線;都不匹配時使用上方預設流程線',
|
||
addRoutingRule: '新增規則',
|
||
ruleTypeLauncherType: '會話類型',
|
||
ruleTypeLauncherId: '會話 ID',
|
||
ruleTypeMessageContent: '訊息內容',
|
||
operatorEq: '等於',
|
||
operatorNeq: '不等於',
|
||
operatorContains: '包含',
|
||
operatorNotContains: '不包含',
|
||
operatorStartsWith: '前綴匹配',
|
||
operatorRegex: '正規表達式',
|
||
operatorHas: '包含',
|
||
operatorNotHas: '不包含',
|
||
ruleTypeMessageHasElement: '訊息元素類型',
|
||
ruleValueElementPlaceholder: '選擇元素類型',
|
||
elementImage: '圖片',
|
||
elementVoice: '語音',
|
||
elementFile: '檔案',
|
||
elementForward: '轉發',
|
||
elementFace: '表情',
|
||
elementAt: '@某人',
|
||
elementAtAll: '@全體',
|
||
elementQuote: '引用',
|
||
ruleValuePlaceholder: '匹配值',
|
||
ruleValueLauncherIdPlaceholder: '群組或使用者 ID',
|
||
ruleValueMessagePlaceholder: '訊息內容',
|
||
ruleValuePrefixPlaceholder: '如: !draw',
|
||
ruleValueRegexpPlaceholder: '如: ^/help',
|
||
pipelineDiscard: '丟棄訊息',
|
||
sessionTypePerson: '私聊',
|
||
sessionTypeGroup: '群聊',
|
||
adapterConfigDescription: '設定所選平台適配器',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '不可逆的操作',
|
||
deleteBotAction: '刪除此機器人',
|
||
deleteBotHint: '刪除後,所有關聯設定將被永久移除,且無法復原。',
|
||
webhookUrl: 'Webhook 回調位址',
|
||
webhookUrlCopied: 'Webhook 位址已複製',
|
||
webhookUrlHint:
|
||
'點擊輸入框自動全選,然後按 Ctrl+C (Mac: Cmd+C) 複製,或點擊右側按鈕',
|
||
webhookUrlHintEither: '以上兩個地址任選其一填入平台配置即可',
|
||
webhookSaasHint:
|
||
'Webhook 需要公網可存取的網域。LangBot Cloud 為你的機器人提供即開即用的公網位址。',
|
||
webhookSaasLink: '了解 LangBot Cloud',
|
||
adapterCategory: {
|
||
popular: '熱門',
|
||
china: '中國',
|
||
global: '全球',
|
||
protocol: '協定',
|
||
},
|
||
logLevel: '日誌級別',
|
||
allLevels: '全部級別',
|
||
selectLevel: '選擇級別',
|
||
levelsSelected: '個級別已選',
|
||
viewDetailedLogs: '查看詳細日誌',
|
||
viewDetails: '詳情',
|
||
collapse: '收起',
|
||
imagesAttached: '張圖片已附加',
|
||
noLogs: '暫無日誌',
|
||
sessionMonitor: {
|
||
title: '會話監控',
|
||
sessions: '會話列表',
|
||
noSessions: '暫無會話',
|
||
selectSession: '選擇一個會話查看訊息',
|
||
noMessages: '該會話暫無訊息',
|
||
messages: '條訊息',
|
||
messageCount: '{{count}} 條訊息',
|
||
loading: '載入中...',
|
||
loadingSessions: '載入會話中...',
|
||
loadingMessages: '載入訊息中...',
|
||
user: '用戶',
|
||
variables: '變數',
|
||
platform: '平台',
|
||
lastActive: '最近活躍',
|
||
refresh: '重新整理',
|
||
active: '活躍',
|
||
inactive: '不活躍',
|
||
discarded: '已丟棄',
|
||
userMessage: '使用者',
|
||
botMessage: '助手',
|
||
},
|
||
},
|
||
plugins: {
|
||
title: '外掛擴展',
|
||
description: '安裝和設定用於擴展功能的外掛,請在流程線配置中選用',
|
||
createPlugin: '建立外掛',
|
||
editPlugin: '編輯外掛',
|
||
installed: '已安裝',
|
||
marketplace: 'Marketplace',
|
||
arrange: '編排',
|
||
install: '安裝',
|
||
installPlugin: '安裝外掛',
|
||
newPlugin: '新建外掛',
|
||
installFromGithub: '來自 GitHub',
|
||
onlySupportGithub: '目前僅支援從 GitHub 安裝',
|
||
enterGithubLink: '請輸入外掛的Github連結',
|
||
installing: '正在安裝外掛...',
|
||
installSuccess: '外掛安裝成功',
|
||
installFailed: '外掛安裝失敗:',
|
||
searchPlugin: '搜尋外掛',
|
||
sortBy: '排序方式',
|
||
mostStars: '最多星標',
|
||
recentlyAdded: '最近新增',
|
||
recentlyUpdated: '最近更新',
|
||
noMatchingPlugins: '沒有找到符合的外掛',
|
||
loading: '載入中...',
|
||
getPluginListError: '取得外掛清單失敗:',
|
||
pluginConfig: '外掛設定',
|
||
noPluginInstalled: '暫未安裝任何外掛',
|
||
noExtensionInstalled: '暫未安裝任何擴充功能',
|
||
loadingExtensions: '正在載入擴充功能...',
|
||
groupByType: '依格式分組',
|
||
pluginSort: '外掛排序',
|
||
pluginSortDescription:
|
||
'外掛順序會影響同一事件內的處理順序,請拖曳外掛卡片排序',
|
||
pluginSortSuccess: '外掛排序成功',
|
||
pluginSortError: '外掛排序失敗:',
|
||
pluginNoConfig: '外掛沒有設定項目。',
|
||
systemDisabled: '外掛系統未啟用',
|
||
systemDisabledDesc: '尚未啟用外掛系統,請根據文檔修改配置',
|
||
connectionError: '外掛系統連接異常',
|
||
connectionErrorDesc: '請檢查外掛系統配置或聯絡管理員',
|
||
errorDetails: '錯誤詳情',
|
||
loadingStatus: '正在檢查外掛系統狀態...',
|
||
failedToGetStatus: '取得外掛系統狀態失敗',
|
||
pluginSystemNotReady: '外掛系統未就緒,無法執行此操作',
|
||
debugInfo: '偵錯資訊',
|
||
debugInfoTitle: '外掛偵錯資訊',
|
||
debugUrl: '偵錯位址',
|
||
debugKey: '偵錯金鑰',
|
||
noDebugKey: '(未設定)',
|
||
debugKeyDisabled: '未設定偵錯金鑰,外掛偵錯無需認證',
|
||
boxStatusTitle: 'Box 執行時',
|
||
boxStatus: '狀態',
|
||
boxConnected: '已連線',
|
||
boxUnavailable: '不可用',
|
||
boxBackend: '後端',
|
||
boxProfile: '設定檔',
|
||
boxSandboxes: '沙箱數',
|
||
boxErrors: '錯誤數',
|
||
boxSessionImage: '映像檔',
|
||
boxSessionBackend: '後端',
|
||
boxSessionResources: '資源',
|
||
boxSessionNetwork: '網路',
|
||
boxStatusLoadFailed: '載入 Box 狀態失敗',
|
||
failedToGetDebugInfo: '取得偵錯資訊失敗',
|
||
copiedToClipboard: '已複製到剪貼簿',
|
||
deleting: '刪除中...',
|
||
deletePlugin: '刪除外掛',
|
||
cancel: '取消',
|
||
saveConfig: '儲存設定',
|
||
saving: '儲存中...',
|
||
confirmDeletePlugin: '您確定要刪除外掛({{author}}/{{name}})嗎?',
|
||
deleteDataCheckbox: '同時刪除外掛設定和持久化儲存',
|
||
confirmDelete: '確認刪除',
|
||
deleteError: '刪除失敗:',
|
||
close: '關閉',
|
||
deleteConfirm: '刪除確認',
|
||
deleteSuccess: '刪除成功',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '不可逆的操作',
|
||
modifyFailed: '修改失敗:',
|
||
componentName: {
|
||
Tool: '工具',
|
||
EventListener: '事件監聽器',
|
||
Command: '命令',
|
||
KnowledgeEngine: '知識引擎',
|
||
Parser: '解析器',
|
||
Page: '擴展頁',
|
||
},
|
||
uploadLocal: '本地上傳',
|
||
debugging: '調試中',
|
||
uploadLocalPlugin: '上傳本地插件',
|
||
localPreview: {
|
||
title: '預覽本地外掛包',
|
||
unpacking: '正在解包預覽...',
|
||
unpackComplete: '解包預覽完成',
|
||
failed: '解包預覽失敗',
|
||
pluginInfo: '外掛資訊',
|
||
packageInfo: '包資訊',
|
||
name: '名稱',
|
||
author: '作者',
|
||
version: '版本',
|
||
fileCount: '檔案數',
|
||
dependencies: '依賴',
|
||
components: '元件',
|
||
ready: '外掛包已解包,確認後開始安裝。',
|
||
},
|
||
dragToUpload: '拖拽文件到此處上傳',
|
||
unsupportedFileType: '不支持的文件類型,僅支持 .lbpkg 和 .zip 文件',
|
||
uploadingPlugin: '正在上傳插件...',
|
||
uploadSuccess: '上傳成功',
|
||
uploadFailed: '上傳失敗',
|
||
selectFileToUpload: '選擇要上傳的插件文件',
|
||
askConfirm: '確定要安裝插件 "{{name}}" ({{version}}) 嗎?',
|
||
askConfirmNoVersion: '確定要安裝插件 "{{name}}" 嗎?',
|
||
fromGithub: '來自 GitHub',
|
||
fromLocal: '本地安裝',
|
||
fromMarketplace: '來自市場',
|
||
componentsList: '組件: ',
|
||
noComponents: '無組件',
|
||
delete: '刪除插件',
|
||
update: '更新插件',
|
||
new: '新',
|
||
updateConfirm: '更新確認',
|
||
confirmUpdatePlugin: '您確定要更新插件({{author}}/{{name}})嗎?',
|
||
confirmUpdate: '確認更新',
|
||
updating: '更新中...',
|
||
updateSuccess: '插件更新成功',
|
||
updateError: '更新失敗:',
|
||
saveConfigSuccessNormal: '儲存配置成功',
|
||
saveConfigError: '儲存配置失敗:',
|
||
config: '配置',
|
||
readme: '文件',
|
||
viewSource: '查看來源',
|
||
loadingReadme: '正在載入文件...',
|
||
noReadme: '該插件沒有提供 README 文件',
|
||
fileUpload: {
|
||
tooLarge: '檔案大小超過 10MB 限制',
|
||
success: '檔案上傳成功',
|
||
failed: '檔案上傳失敗',
|
||
uploading: '上傳中...',
|
||
chooseFile: '選擇檔案',
|
||
addFile: '新增檔案',
|
||
},
|
||
enterRepoUrl: '請輸入 GitHub 倉庫地址',
|
||
repoUrlPlaceholder: '例如: https://github.com/owner/repo',
|
||
fetchingReleases: '正在獲取 Release 列表...',
|
||
selectRelease: '選擇 Release',
|
||
noReleasesFound: '未找到 Release',
|
||
fetchReleasesError: '獲取 Release 列表失敗:',
|
||
selectAsset: '選擇要安裝的文件',
|
||
noAssetsFound: '該 Release 沒有可用的 .lbpkg 文件',
|
||
fetchAssetsError: '獲取文件列表失敗:',
|
||
backToReleases: '返回 Release 列表',
|
||
backToRepoUrl: '返回倉庫地址',
|
||
backToAssets: '返回文件選擇',
|
||
releaseTag: 'Tag: {{tag}}',
|
||
releaseName: '名稱: {{name}}',
|
||
publishedAt: '發佈於: {{date}}',
|
||
prerelease: '預發佈',
|
||
assetSize: '大小: {{size}}',
|
||
confirmInstall: '確認安裝',
|
||
installFromGithubDesc: '從 GitHub Release 安裝插件',
|
||
goToMarketplace: '前往外掛市場',
|
||
installProgress: {
|
||
title: '正在安裝 {{name}}',
|
||
titleGeneric: '外掛安裝',
|
||
overallProgress: '整體進度',
|
||
downloading: '下載外掛',
|
||
installingDeps: '安裝依賴',
|
||
initializing: '初始化設定',
|
||
launching: '啟動外掛',
|
||
completed: '已完成',
|
||
failed: '安裝失敗',
|
||
downloadSize: '檔案大小: {{size}}',
|
||
depsInfo: '共 {{count}} 個依賴需要安裝',
|
||
depsProgress: '已安裝 {{installed}}/{{total}} · 剩餘 {{remaining}} 個',
|
||
installComplete: '外掛安裝成功',
|
||
dismiss: '關閉',
|
||
background: '背景執行',
|
||
taskQueue: '安裝任務',
|
||
clearCompleted: '清除已完成',
|
||
noTasks: '暫無安裝任務',
|
||
titlePlugin: '正在安裝外掛 {{name}}',
|
||
titleMCP: '正在安裝 MCP 伺服器 {{name}}',
|
||
titleSkill: '正在安裝技能 {{name}}',
|
||
installCompletePlugin: '外掛安裝成功',
|
||
installCompleteMCP: 'MCP 伺服器安裝成功',
|
||
installCompleteSkill: '技能安裝成功',
|
||
},
|
||
uploadPluginOnly: '僅支援 .lbpkg 外掛包',
|
||
},
|
||
market: {
|
||
searchPlaceholder: '搜尋插件...',
|
||
searchResults: '搜尋到 {{count}} 個插件',
|
||
totalPlugins: '共 {{count}} 個插件',
|
||
noPlugins: '暫無插件',
|
||
noResults: '未找到相關插件',
|
||
loadingMore: '載入更多...',
|
||
loading: '載入中...',
|
||
allLoaded: '已顯示全部插件',
|
||
install: '安裝',
|
||
installCard: '安裝 {{name}}',
|
||
installConfirm: '確定要安裝插件 "{{name}}" ({{version}}) 嗎?',
|
||
downloadComplete: '插件 "{{name}}" 下載完成',
|
||
installFailed: '安裝失敗,請稍後重試',
|
||
loadFailed: '取得插件列表失敗,請稍後重試',
|
||
noDescription: '暫無描述',
|
||
notFound: '插件資訊未找到',
|
||
sortBy: '排序方式',
|
||
sort: {
|
||
recentlyAdded: '最近新增',
|
||
recentlyUpdated: '最近更新',
|
||
mostDownloads: '最多下載',
|
||
leastDownloads: '最少下載',
|
||
},
|
||
downloads: '次下載',
|
||
download: '下載',
|
||
repository: '代碼倉庫',
|
||
downloadFailed: '下載失敗',
|
||
noReadme: '該插件沒有提供 README 文件',
|
||
description: '描述',
|
||
tagLabel: '標籤',
|
||
submissionTitle: '您有插件提交正在審核中: {{name}}',
|
||
submissionPending: '您的插件提交正在審核中: {{name}}',
|
||
submissionApproved: '您的插件提交已通過審核: {{name}}',
|
||
submissionRejected: '您的插件提交已被拒絕: {{name}}',
|
||
clickToRevoke: '撤回',
|
||
revokeSuccess: '撤回成功',
|
||
revokeFailed: '撤回失敗',
|
||
submissionDetails: '插件提交詳情',
|
||
markAsRead: '已讀',
|
||
markAsReadSuccess: '已標記為已讀',
|
||
markAsReadFailed: '標記為已讀失敗',
|
||
filterByComponent: '插件組件',
|
||
filterByComponentHint:
|
||
'插件提供的能力類型,如工具(Tool)、命令(Command)、事件監聽器(EventListener)等,用於擴展 LangBot 的各項能力。按組件篩選可只看提供對應能力的插件。',
|
||
allComponents: '全部組件',
|
||
componentName: {
|
||
Tool: '工具',
|
||
EventListener: '事件監聽器',
|
||
Command: '命令',
|
||
KnowledgeEngine: '知識引擎',
|
||
Parser: '解析器',
|
||
Page: '擴展頁',
|
||
},
|
||
filterByType: '類型',
|
||
allTypes: '全部類型',
|
||
typePlugin: '插件',
|
||
typeMCP: 'MCP',
|
||
typeSkill: '技能',
|
||
requestPlugin: '請求插件',
|
||
tags: {
|
||
filterByTags: '按標籤篩選',
|
||
selected: '已選',
|
||
selectTags: '選擇標籤',
|
||
clearAll: '清空',
|
||
noTags: '暫無標籤',
|
||
},
|
||
filters: {
|
||
allFormats: '全部類型',
|
||
more: '更多',
|
||
advancedTitle: '高級篩選',
|
||
advancedDescription: '按擴展類型篩選',
|
||
technicalType: '技術類型',
|
||
},
|
||
allExtensions: '全部擴展',
|
||
viewDetails: '查看詳情',
|
||
deprecated: '已棄用',
|
||
deprecatedTooltip: '請安裝對應「知識引擎」插件',
|
||
},
|
||
mcp: {
|
||
title: 'MCP',
|
||
createServer: '新增MCP伺服器',
|
||
addMCPServer: '新增 MCP 伺服器',
|
||
editServer: '編輯MCP伺服器',
|
||
deleteServer: '刪除MCP伺服器',
|
||
confirmDeleteServer: '您確定要刪除此MCP伺服器嗎?',
|
||
confirmDeleteTitle: '刪除MCP伺服器',
|
||
getServerListError: '取得MCP伺服器清單失敗:',
|
||
serverName: '伺服器名稱',
|
||
serverMode: '連接模式',
|
||
stdio: 'Stdio模式',
|
||
sse: 'SSE模式',
|
||
selectMode: '選擇連接模式',
|
||
http: 'HTTP模式',
|
||
noServerInstalled: '暫未設定任何MCP伺服器',
|
||
serverNameRequired: '伺服器名稱不能為空',
|
||
commandRequired: '命令不能為空',
|
||
urlRequired: 'URL不能為空',
|
||
timeoutMustBePositive: '逾時時間必須是正數',
|
||
command: '命令',
|
||
args: '參數',
|
||
env: '環境變數',
|
||
url: 'URL位址',
|
||
headers: '請求標頭',
|
||
timeout: '逾時時間',
|
||
addArgument: '新增參數',
|
||
addEnvVar: '新增環境變數',
|
||
addHeader: '新增請求標頭',
|
||
keyName: '鍵名',
|
||
value: '值',
|
||
testing: '測試中...',
|
||
connecting: '連接中...',
|
||
testSuccess: '測試成功',
|
||
testFailed: '刷新失敗:',
|
||
testError: '刷新出錯',
|
||
refreshSuccess: '刷新成功',
|
||
refreshFailed: '刷新失敗:',
|
||
connectionSuccess: '連接成功',
|
||
connectionFailed: '連接失敗,請檢查URL',
|
||
connectionFailedStatus: '連接失敗',
|
||
boxDisabledStdioRefused:
|
||
'Stdio 模式的 MCP 伺服器依賴 Box 沙箱,目前已在設定中停用(box.enabled = false)。',
|
||
boxUnavailableStdioRefused:
|
||
'Stdio 模式的 MCP 伺服器依賴 Box 沙箱,目前無法連線。',
|
||
boxStdioRefusedSuggestion:
|
||
'請啟用 Box(box.enabled = true)並確認執行時連線正常,或將此伺服器切換到 http/sse 模式。',
|
||
boxRequired: '需要 Box',
|
||
stdioBlockedByBoxToast:
|
||
'Box 沙箱已停用或無法使用,無法儲存 stdio 模式的 MCP。請啟用 Box 或改為 http/sse 模式。',
|
||
toolsFound: '個工具',
|
||
unknownError: '未知錯誤',
|
||
noToolsFound: '未找到任何工具',
|
||
parseResultFailed: '解析測試結果失敗',
|
||
noResultReturned: '測試未返回結果',
|
||
getTaskFailed: '獲取任務狀態失敗',
|
||
noTaskId: '未獲取到任務ID',
|
||
deleteSuccess: '刪除成功',
|
||
deleteFailed: '刪除失敗:',
|
||
deleteError: '刪除失敗:',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗:',
|
||
createSuccess: '建立成功',
|
||
createFailed: '建立失敗:',
|
||
createError: '建立失敗:',
|
||
loadFailed: '載入失敗',
|
||
modifyFailed: '修改失敗:',
|
||
toolCount: '工具:{{count}}',
|
||
parameterCount: '參數:{{count}}',
|
||
noParameters: '無參數',
|
||
statusConnected: '已連線',
|
||
statusDisconnected: '未連線',
|
||
statusError: '連接錯誤',
|
||
statusDisabled: '已停用',
|
||
loading: '載入中...',
|
||
starCount: '星標:{{count}}',
|
||
install: '安裝',
|
||
installFromGithub: '從Github安裝MCP伺服器',
|
||
add: '新增',
|
||
name: '名稱',
|
||
nameRequired: '名稱不能為空',
|
||
sseTimeout: 'SSE逾時時間',
|
||
sseTimeoutDescription: '用於建立SSE連接的逾時時間',
|
||
extraParametersDescription: '額外參數,用於設定MCP伺服器的特定行為',
|
||
timeoutMustBeNumber: '逾時時間必須是數字',
|
||
timeoutNonNegative: '逾時時間不能為負數',
|
||
sseTimeoutMustBeNumber: 'SSE逾時時間必須是數字',
|
||
sseTimeoutNonNegative: 'SSE逾時時間不能為負數',
|
||
updateSuccess: '更新成功',
|
||
updateFailed: '更新失敗:',
|
||
selectFromSidebar: '從側邊欄選擇一個 MCP 伺服器',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '此 MCP 伺服器的不可逆操作。',
|
||
deleteMCPAction: '刪除此 MCP 伺服器',
|
||
deleteMCPHint: '刪除後,此 MCP 伺服器設定將無法恢復。',
|
||
},
|
||
pipelines: {
|
||
title: '流程線',
|
||
description: '流程線定義了對訊息事件的處理流程,用於綁定到機器人',
|
||
createPipeline: '建立流程線',
|
||
selectFromSidebar: '從側邊欄選擇一個流程線',
|
||
editPipeline: '編輯流程線',
|
||
chat: '對話',
|
||
configuration: '設定',
|
||
debugChat: '對話除錯',
|
||
getPipelineListError: '取得流程線清單失敗:',
|
||
daysAgo: '天前',
|
||
today: '今天',
|
||
updateTime: '更新於',
|
||
defaultBadge: '預設',
|
||
sortBy: '排序方式',
|
||
newestCreated: '最新建立',
|
||
earliestCreated: '最早建立',
|
||
recentlyEdited: '最近編輯',
|
||
earliestEdited: '最早編輯',
|
||
basicInfo: '基本資訊',
|
||
basicInfoDescription: '設定流程線名稱、圖示和描述',
|
||
aiCapabilities: 'AI 能力',
|
||
triggerConditions: '觸發條件',
|
||
safetyControls: '安全控制',
|
||
outputProcessing: '輸出處理',
|
||
nameRequired: '名稱不能為空',
|
||
descriptionRequired: '描述不能為空',
|
||
createSuccess: '建立成功 請編輯流程線詳細參數',
|
||
createError: '建立失敗:',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗:',
|
||
copySuffix: ' Copy',
|
||
deleteConfirmation:
|
||
'您確定要刪除這個流程線嗎?已綁定此流程線的機器人將無法使用。',
|
||
defaultPipelineCannotDelete: '預設流程線不可刪除',
|
||
deleteSuccess: '刪除成功',
|
||
deleteError: '刪除失敗:',
|
||
copyConfirmTitle: '確認複製',
|
||
copyConfirmation:
|
||
'確定要複製這個流程線嗎?複製將創建一個包含完整配置的新流程線。',
|
||
unsavedChanges: '有未儲存的變更',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '不可逆的操作',
|
||
deletePipelineAction: '刪除此流程線',
|
||
deletePipelineHint: '刪除後,綁定此流程線的機器人將無法正常運作。',
|
||
copyPipelineAction: '複製此流程線',
|
||
copyPipelineHint: '建立一條新的流程線,並複製所有設定。',
|
||
extensions: {
|
||
title: '擴展集成',
|
||
loadError: '載入插件清單失敗',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗',
|
||
noPluginsAvailable: '暫無可用插件',
|
||
disabled: '已停用',
|
||
noPluginsSelected: '未選擇任何插件',
|
||
addPlugin: '新增插件',
|
||
selectPlugins: '選擇插件',
|
||
pluginsTitle: '插件',
|
||
mcpServersTitle: 'MCP 伺服器',
|
||
noMCPServersSelected: '未選擇任何 MCP 伺服器',
|
||
addMCPServer: '新增 MCP 伺服器',
|
||
selectMCPServers: '選擇 MCP 伺服器',
|
||
toolCount: '{{count}} 個工具',
|
||
noPluginsInstalled: '無已安裝的插件',
|
||
noMCPServersConfigured: '無已配置的 MCP 伺服器',
|
||
selectAll: '全選',
|
||
enableAllPlugins: '啟用所有插件',
|
||
enableAllMCPServers: '啟用所有 MCP 伺服器',
|
||
allPluginsEnabled: '已啟用所有插件',
|
||
allMCPServersEnabled: '已啟用所有 MCP 伺服器',
|
||
enableAllSkills: '啟用全部技能',
|
||
allSkillsEnabled: '已啟用全部技能',
|
||
skillsTitle: '技能',
|
||
noSkillsSelected: '未選擇技能',
|
||
addSkill: '新增技能',
|
||
selectSkills: '選擇技能',
|
||
noSkillsAvailable: '暫無可用技能',
|
||
},
|
||
debugDialog: {
|
||
title: '流程線對話',
|
||
selectPipeline: '選擇流程線',
|
||
sessionType: '對話類型',
|
||
privateChat: '私聊',
|
||
groupChat: '群聊',
|
||
send: '傳送',
|
||
reset: '重設對話',
|
||
inputPlaceholder: '傳送 {{type}} 訊息...',
|
||
noMessages: '暫無訊息',
|
||
userMessage: '使用者',
|
||
botMessage: '機器人',
|
||
sendFailed: '傳送失敗',
|
||
resetSuccess: '對話已重設',
|
||
resetFailed: '重設失敗',
|
||
loadMessagesFailed: '載入訊息失敗',
|
||
loadPipelinesFailed: '載入流程線失敗',
|
||
atTips: '提及機器人',
|
||
streaming: '串流傳輸',
|
||
streamOutput: '串流',
|
||
connected: 'WebSocket已連接',
|
||
disconnected: 'WebSocket未連接',
|
||
connectionError: 'WebSocket連接錯誤',
|
||
connectionFailed: 'WebSocket連接失敗',
|
||
notConnected: 'WebSocket未連接,請稍後重試',
|
||
imageUploadFailed: '圖片上傳失敗',
|
||
reply: '回覆',
|
||
replyTo: '回覆給',
|
||
showMarkdown: '渲染',
|
||
showRaw: '原文',
|
||
allMembers: '全體成員',
|
||
file: '檔案',
|
||
voice: '語音',
|
||
uploadImage: '上傳圖片',
|
||
uploading: '上傳中...',
|
||
},
|
||
monitoring: {
|
||
title: '監控日誌',
|
||
description: '檢視此流程線的執行記錄和錯誤資訊(最近24小時)',
|
||
detailedLogs: '詳細日誌',
|
||
},
|
||
},
|
||
knowledge: {
|
||
title: '知識庫',
|
||
createKnowledgeBase: '建立知識庫',
|
||
selectFromSidebar: '從側邊欄選擇一個知識庫',
|
||
editKnowledgeBase: '編輯知識庫',
|
||
selectKnowledgeBase: '選擇知識庫',
|
||
selectKnowledgeBases: '選擇知識庫',
|
||
addKnowledgeBase: '新增知識庫',
|
||
noKnowledgeBaseSelected: '未選擇知識庫',
|
||
empty: '無',
|
||
editDocument: '文檔',
|
||
description: '設定可用於提升模型回覆品質的知識庫',
|
||
metadata: '中繼資料',
|
||
documents: '文檔',
|
||
kbNameRequired: '知識庫名稱不能為空',
|
||
kbDescriptionRequired: '知識庫描述不能為空',
|
||
embeddingModelUUIDRequired: '嵌入模型不能為空',
|
||
daysAgo: '天前',
|
||
today: '今天',
|
||
kbName: '知識庫名稱',
|
||
kbDescription: '知識庫描述',
|
||
topK: '召回數量 ',
|
||
topKRequired: '召回數量不能為空',
|
||
topKMax: '召回數量最大值為30',
|
||
topKdescription: '取得相關性高的上位 K 件文獻的數量,範圍為1~30',
|
||
defaultDescription: '一個知識庫',
|
||
embeddingModelUUID: '嵌入模型',
|
||
selectEmbeddingModel: '選擇嵌入模型',
|
||
embeddingModelDescription: '用於向量化文字,可在模型設定頁面設定',
|
||
updateTime: '更新於',
|
||
cannotChangeEmbeddingModel: '知識庫建立後不可修改嵌入模型',
|
||
updateKnowledgeBaseSuccess: '知識庫更新成功',
|
||
updateKnowledgeBaseFailed: '知識庫更新失敗:',
|
||
documentsTab: {
|
||
name: '名稱',
|
||
status: '狀態',
|
||
noResults: '暫無文件',
|
||
dragAndDrop: '拖曳文檔到此處或點擊上傳',
|
||
uploading: '上傳中...',
|
||
supportedFormats: '支援 PDF、Word、TXT、Markdown 等文檔格式',
|
||
uploadSuccess: '文檔上傳成功!',
|
||
uploadError: '文檔上傳失敗:',
|
||
uploadingFile: '上傳文檔中...',
|
||
fileSizeExceeded: '檔案大小超過 10MB 限制,請分割成較小的檔案後上傳',
|
||
actions: '操作',
|
||
delete: '刪除文檔',
|
||
fileDeleteSuccess: '文檔刪除成功',
|
||
fileDeleteFailed: '文檔刪除失敗:',
|
||
processing: '處理中',
|
||
completed: '完成',
|
||
failed: '失敗',
|
||
selectParser: '選擇解析器',
|
||
builtInParser: '由知識引擎提供',
|
||
noParserAvailable:
|
||
'沒有解析器支援此檔案類型,請安裝支援該格式的解析器插件。',
|
||
installParserHint: '前往插件市場安裝解析器 →',
|
||
confirmUpload: '上傳',
|
||
cancelUpload: '取消',
|
||
},
|
||
deleteKnowledgeBaseConfirmation:
|
||
'您確定要刪除這個知識庫嗎?此知識庫下的所有文檔將被刪除。',
|
||
retrieve: '檢索測試',
|
||
retrieveTest: '檢索測試',
|
||
query: '查詢',
|
||
queryPlaceholder: '輸入查詢內容...',
|
||
distance: '距離',
|
||
content: '內容',
|
||
fileName: '文檔名稱',
|
||
noResults: '暫無結果',
|
||
retrieveError: '檢索失敗:',
|
||
basicInfo: '基本資訊',
|
||
basicInfoDescription: '設定知識庫名稱、圖示和描述',
|
||
engineSettings: '引擎設定',
|
||
engineSettingsDescription: '所選知識引擎的設定',
|
||
engineSettingsReadonly: '編輯模式下不可修改',
|
||
engineSettingsInvalid: '引擎設定中存在無效項,請檢查必填欄位',
|
||
retrievalSettingsInvalid: '檢索設定中存在無效項,請檢查必填欄位',
|
||
retrievalSettings: '檢索設定',
|
||
retrievalSettingsDescription: '設定從此知識庫檢索文件的方式',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '不可逆的操作',
|
||
deleteKbAction: '刪除此知識庫',
|
||
deleteKbHint: '刪除後,此知識庫中的所有文件和資料將被永久移除。',
|
||
noEnginesAvailable: '沒有可用的知識庫引擎',
|
||
installEngineHint: '請先安裝「知識引擎」插件',
|
||
unknownEngine: '未知引擎',
|
||
knowledgeEngine: '知識引擎',
|
||
knowledgeEngineRequired: '知識引擎為必填項',
|
||
selectKnowledgeEngine: '選擇知識引擎',
|
||
builtInEngine: '內建引擎',
|
||
cannotChangeKnowledgeEngine: '建立後無法更改知識引擎',
|
||
createKnowledgeBaseFailed: '知識庫建立失敗:',
|
||
loadKnowledgeBaseFailed: '知識庫載入失敗:',
|
||
deleteKnowledgeBaseFailed: '知識庫刪除失敗:',
|
||
getKnowledgeBaseListError: '取得知識庫列表失敗:',
|
||
embeddingModel: 'Embedding模型',
|
||
embeddingModelRequired: '此引擎需要Embedding模型',
|
||
addExternal: '添加外部知識庫',
|
||
createExternalSuccess: '外部知識庫創建成功',
|
||
updateExternalSuccess: '外部知識庫更新成功',
|
||
deleteExternalSuccess: '外部知識庫刪除成功',
|
||
retriever: '檢索器',
|
||
selectRetriever: '選擇一個檢索器...',
|
||
retrieverConfiguration: '檢索器配置',
|
||
retrieverInstallInfo: '您可以從',
|
||
retrieverMarketLink: '此處安裝知識檢索器插件',
|
||
migration: {
|
||
title: '知識庫遷移',
|
||
description:
|
||
'新版本已將知識庫重構為插件化架構,並統一內建知識庫和外部知識庫為「知識引擎」插件,需要對舊知識庫資料進行遷移。您的舊資料已自動備份在資料庫中。',
|
||
detected:
|
||
'共檢測到 {{total}} 個知識庫需要遷移({{internal}} 個內建知識庫,{{external}} 個外部知識庫)。',
|
||
startWithInstall: '自動安裝插件並遷移',
|
||
startDataOnly: '僅遷移資料',
|
||
dataOnlyHint:
|
||
'「僅遷移資料」適合內網環境使用,請在遷移完成後自行安裝對應插件',
|
||
dismiss: '丟棄原數據',
|
||
running: '正在遷移知識庫,請稍候...',
|
||
success: '知識庫遷移完成',
|
||
error: '知識庫遷移失敗:',
|
||
dismissError: '操作失敗',
|
||
retry: '重試',
|
||
},
|
||
},
|
||
register: {
|
||
title: '初始化 LangBot 👋',
|
||
description: '這是您首次啟動 LangBot',
|
||
adminAccountNote: '您在此處初始化使用的帳號將作為管理員帳號',
|
||
register: '註冊',
|
||
initWithSpace: '透過 Space 初始化',
|
||
spaceRecommended: '推薦:使用官方提供的穩定模型 API 和雲服務',
|
||
spaceInfoTip1: 'Space 提供統一的帳戶鑑權服務,不會上傳您的任何敏感資訊。',
|
||
spaceInfoTip2:
|
||
'使用 Space 帳戶登入可使用 LangBot Models 等雲服務,您將會獲得一定的免費模型調用額度幫助您快速起步。',
|
||
spaceInfoTip3:
|
||
'登入方式不會影響其他功能,您在任何情況下都可以配置使用其他來源的模型。',
|
||
registerLocal: '註冊本地帳號',
|
||
registerWithPassword: '透過電子郵件密碼組合註冊',
|
||
initSuccess: '初始化成功 請登入',
|
||
initFailed: '初始化失敗:',
|
||
},
|
||
resetPassword: {
|
||
title: '重設密碼 🔐',
|
||
description: '輸入恢復金鑰和新的密碼來重設您的帳戶密碼',
|
||
recoveryKey: '恢復金鑰',
|
||
recoveryKeyDescription:
|
||
'儲存在設定檔案`data/config.yaml`的`system.recovery_key`中',
|
||
newPassword: '新密碼',
|
||
enterRecoveryKey: '輸入恢復金鑰',
|
||
enterNewPassword: '輸入新密碼',
|
||
recoveryKeyRequired: '恢復金鑰不能為空',
|
||
newPasswordRequired: '新密碼不能為空',
|
||
resetPassword: '重設密碼',
|
||
resetting: '重設中...',
|
||
resetSuccess: '密碼重設成功,請登入',
|
||
resetFailed: '密碼重設失敗,請檢查電子郵件和恢復金鑰是否正確',
|
||
backToLogin: '返回登入',
|
||
},
|
||
embedding: {
|
||
description: '管理嵌入模型,用於向量化文字',
|
||
createModel: '建立嵌入模型',
|
||
editModel: '編輯嵌入模型',
|
||
getModelListError: '取得嵌入模型清單失敗:',
|
||
embeddingModels: '嵌入模型',
|
||
extraParametersDescription:
|
||
'將在請求時附加到請求體中,如 encoding_format, dimensions 等',
|
||
},
|
||
llm: {
|
||
llmModels: '對話模型',
|
||
description: '管理 LLM 模型,用於對話訊息產生',
|
||
extraParametersDescription:
|
||
'將在請求時附加到請求體中,如 max_tokens, temperature, top_p 等',
|
||
},
|
||
version: {
|
||
newVersionAvailable: '有新版本可用',
|
||
viewUpdateGuide: '查看更新方式',
|
||
noReleaseNotes: '暫無更新日誌',
|
||
},
|
||
account: {
|
||
settings: '帳戶設定',
|
||
setPassword: '設定密碼',
|
||
passwordSetSuccess: '密碼設定成功',
|
||
passwordStatus: '本地密碼',
|
||
passwordSet: '已設定',
|
||
passwordNotSet: '未設定',
|
||
passwordSetDescription: '您已設定本地密碼,可使用電子郵件密碼登入',
|
||
spaceStatus: 'Space 帳戶',
|
||
spaceBound: '已綁定',
|
||
spaceNotBound: '未綁定',
|
||
spaceBoundDescription: '已綁定 Space 帳戶,可使用官方模型 API 和雲服務',
|
||
bindSpace: '綁定 Space 帳戶',
|
||
bindSpaceDescription: '綁定後可使用官方模型 API 和雲服務',
|
||
bindSpaceButton: '綁定',
|
||
bindSpaceConfirmTitle: '確認綁定',
|
||
bindSpaceConfirmDescription: '您即將把本地實例綁定到 Space 帳戶',
|
||
bindSpaceWarning:
|
||
'綁定後,您的登入電子郵件將從 {{localEmail}} 更改為 Space 帳戶的電子郵件。',
|
||
bindSpaceSuccess: 'Space 帳戶綁定成功',
|
||
bindSpaceFailed: '綁定 Space 帳戶失敗',
|
||
bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起',
|
||
setPasswordHint: '設定密碼後可使用電子郵件密碼登入',
|
||
spaceEmailMismatch: 'Space登入帳號電子郵件與本實例帳號電子郵件不匹配',
|
||
},
|
||
monitoring: {
|
||
title: '儀表盤',
|
||
description: '監控機器人活動、LLM調用和系統效能',
|
||
overview: '概覽',
|
||
totalMessages: '總訊息數',
|
||
llmCallsCount: 'LLM調用',
|
||
modelCallsCount: '模型調用',
|
||
successRate: '成功率',
|
||
activeSessions: '活躍會話',
|
||
last24Hours: '最近24小時',
|
||
filters: {
|
||
title: '篩選',
|
||
bot: '機器人',
|
||
pipeline: '流水線',
|
||
allBots: '全部機器人',
|
||
selectBot: '選擇機器人',
|
||
allPipelines: '全部流水線',
|
||
selectPipeline: '選擇流水線',
|
||
loading: '載入中...',
|
||
timeRange: '時間範圍',
|
||
customRange: '自訂範圍',
|
||
from: '從',
|
||
to: '到',
|
||
apply: '套用',
|
||
reset: '重置篩選',
|
||
lastHour: '最近1小時',
|
||
last6Hours: '最近6小時',
|
||
last24Hours: '最近24小時',
|
||
last7Days: '最近7天',
|
||
last30Days: '最近30天',
|
||
},
|
||
tabs: {
|
||
messages: '訊息記錄',
|
||
llmCalls: 'LLM調用',
|
||
embeddingCalls: 'Embedding調用',
|
||
modelCalls: '模型調用',
|
||
sessions: '會話分析',
|
||
feedback: '使用者反饋',
|
||
errors: '錯誤日誌',
|
||
},
|
||
messageList: {
|
||
timestamp: '時間戳記',
|
||
bot: '機器人',
|
||
pipeline: '流水線',
|
||
message: '訊息',
|
||
sessionId: '會話ID',
|
||
status: '狀態',
|
||
actions: '操作',
|
||
viewDetails: '查看詳情',
|
||
copyId: '複製ID',
|
||
noMessages: '未找到訊息',
|
||
noMessagesDescription: '嘗試調整篩選條件或稍後查看',
|
||
loading: '載入訊息中...',
|
||
loadMore: '載入更多',
|
||
autoRefresh: '自動重新整理',
|
||
platform: '平台',
|
||
user: '使用者',
|
||
level: '級別',
|
||
runner: '執行器',
|
||
viewConversation: '顯示對話詳情',
|
||
},
|
||
llmCalls: {
|
||
title: 'LLM呼叫',
|
||
model: '模型',
|
||
tokens: '代幣數',
|
||
duration: '持續時間',
|
||
cost: '成本',
|
||
noData: '未找到LLM調用記錄',
|
||
inputTokens: '輸入代幣',
|
||
outputTokens: '輸出代幣',
|
||
totalTokens: '總代幣數',
|
||
avgDuration: '平均持續時間',
|
||
calls: '呼叫次數',
|
||
},
|
||
embeddingCalls: {
|
||
title: 'Embedding調用',
|
||
model: '模型',
|
||
tokens: '代幣數',
|
||
duration: '持續時間',
|
||
noData: '未找到Embedding調用記錄',
|
||
promptTokens: '輸入代幣',
|
||
totalTokens: '總代幣數',
|
||
inputCount: '輸入數量',
|
||
knowledgeBase: '知識庫',
|
||
queryText: '查詢文字',
|
||
},
|
||
modelCalls: {
|
||
title: '模型調用',
|
||
llmModel: '對話模型',
|
||
embeddingModel: '嵌入模型',
|
||
embeddingCall: '嵌入調用',
|
||
retrieveCall: '檢索調用',
|
||
noData: '未找到模型調用記錄',
|
||
},
|
||
sessions: {
|
||
sessionId: '會話ID',
|
||
messageCount: '訊息數',
|
||
duration: '持續時間',
|
||
lastActivity: '最後活動',
|
||
noSessions: '未找到會話',
|
||
startTime: '開始時間',
|
||
messageStats: '訊息統計',
|
||
totalMessages: '總訊息數',
|
||
successMessages: '成功',
|
||
errorMessages: '失敗',
|
||
llmStats: 'LLM統計',
|
||
noData: '未找到會話',
|
||
},
|
||
errors: {
|
||
errorType: '錯誤類型',
|
||
errorMessage: '錯誤訊息',
|
||
occurredAt: '發生時間',
|
||
noErrors: '未找到錯誤',
|
||
stackTrace: '堆疊追蹤',
|
||
title: '錯誤',
|
||
},
|
||
feedback: {
|
||
title: '使用者反饋',
|
||
totalFeedback: '總反饋數',
|
||
totalLikes: '按讚數',
|
||
totalDislikes: '按倒讚數',
|
||
satisfactionRate: '滿意度',
|
||
like: '按讚',
|
||
dislike: '按倒讚',
|
||
noFeedback: '暫無反饋',
|
||
noFeedbackDescription: '使用者反饋將在此顯示',
|
||
feedbackList: '反饋列表',
|
||
feedbackContent: '反饋內容',
|
||
contextInfo: '上下文資訊',
|
||
userId: '使用者ID',
|
||
messageId: '訊息ID',
|
||
streamId: '關聯提問ID',
|
||
inaccurateReasons: '不準確原因',
|
||
platform: '平台',
|
||
exportFeedback: '匯出反饋',
|
||
},
|
||
messageDetails: {
|
||
noData: '此查詢沒有LLM調用或錯誤記錄',
|
||
},
|
||
queries: {
|
||
title: '查詢',
|
||
},
|
||
queryVariables: {
|
||
title: '查詢變數',
|
||
},
|
||
trafficChart: {
|
||
title: '流量概覽',
|
||
messages: '訊息',
|
||
llmCalls: 'LLM呼叫',
|
||
noData: '暫無流量資料',
|
||
},
|
||
viewMonitoring: '查看日誌監控',
|
||
refreshData: '重新整理資料',
|
||
exportData: '匯出資料',
|
||
export: {
|
||
title: '匯出資料',
|
||
exporting: '匯出中...',
|
||
messages: '訊息記錄',
|
||
llmCalls: 'LLM 呼叫',
|
||
embeddingCalls: 'Embedding 呼叫',
|
||
errors: '錯誤日誌',
|
||
sessions: '會話記錄',
|
||
feedback: '使用者回饋',
|
||
},
|
||
systemStatus: '系統狀態',
|
||
pluginRuntime: '外掛執行時',
|
||
boxRuntime: 'Box 執行時',
|
||
connected: '已連線',
|
||
disconnected: '未連線',
|
||
disabled: '已停用',
|
||
statusDetail: '狀態',
|
||
pluginDisabled: '外掛系統已停用',
|
||
boxDisabled:
|
||
'Box 沙箱已在設定中停用——沙箱工具、技能新增/編輯與 stdio MCP 均無法使用',
|
||
boxUnavailable:
|
||
'Box 沙箱無法使用——沙箱工具、技能新增/編輯與 stdio MCP 均無法使用',
|
||
boxRequiredHint:
|
||
'此功能需要 Box 執行時。請在設定中啟用(box.enabled = true)並確認執行時連線正常。',
|
||
boxBackend: '後端',
|
||
boxProfile: '設定檔',
|
||
boxSandboxes: '沙箱數',
|
||
boxSessionCreated: '建立時間',
|
||
boxSessionLastUsed: '最後使用',
|
||
},
|
||
storageAnalysis: {
|
||
title: '儲存分析',
|
||
description: '查看儲存占用和可清理檔案',
|
||
openDialog: '查看分析',
|
||
dialogTitle: '儲存分析',
|
||
generatedAt: '生成時間 {{time}}',
|
||
loading: '載入中...',
|
||
refresh: '重新整理',
|
||
totalSize: '總占用',
|
||
binaryStorage: '插件二進位儲存',
|
||
uploadCleanup: '過期上傳檔案',
|
||
logCleanup: '過期日誌',
|
||
sections: '儲存分區',
|
||
monitoringTables: '監控表',
|
||
runtimeTasks: '執行任務',
|
||
cleanupPolicy: '清理策略',
|
||
uploadRetention: '上傳檔案保留',
|
||
logRetention: '日誌保留',
|
||
databaseType: '資料庫類型',
|
||
days: '天',
|
||
missing: '不存在',
|
||
expiredUploads: '過期上傳檔案',
|
||
expiredLogs: '過期日誌',
|
||
noExpiredUploads: '暫無過期上傳檔案',
|
||
noExpiredLogs: '暫無過期日誌',
|
||
sectionNames: {
|
||
database: '資料庫',
|
||
logs: '日誌',
|
||
storage: '上傳檔案',
|
||
vector_store: '向量庫',
|
||
plugins: '插件',
|
||
mcp: 'MCP',
|
||
temp: '暫存檔案',
|
||
},
|
||
},
|
||
limitation: {
|
||
maxBotsReached:
|
||
'已達到機器人數量上限({{max}}個)。請先刪除已有機器人後再建立新的。',
|
||
maxPipelinesReached:
|
||
'已達到流水線數量上限({{max}}個)。請先刪除已有流水線後再建立新的。',
|
||
maxExtensionsReached:
|
||
'已達到擴充功能數量上限({{max}}個)。請先刪除已有擴充功能後再新增。',
|
||
},
|
||
wizard: {
|
||
sidebarDescription: '透過引導步驟建立機器人',
|
||
loading: '正在載入嚮導...',
|
||
loadError: '載入嚮導資料失敗',
|
||
skip: '跳過',
|
||
skipConfirmMessage:
|
||
'您之後可以在帳戶選單重新進入快速開始嚮導,或手動建立機器人。',
|
||
skipConfirmOk: '確定',
|
||
prev: '上一步',
|
||
next: '下一步',
|
||
finish: '建立並部署',
|
||
confirmCreateBot: '確定,建立機器人',
|
||
createSuccess: '流水線已建立並關聯到機器人!',
|
||
botCreateSuccess: '機器人建立成功!',
|
||
botSaveSuccess: '機器人配置已儲存並啟用!',
|
||
createError: '建立資源失敗',
|
||
spaceAuthError: '無法發起 Space 授權',
|
||
skipSaveError: '儲存跳過狀態失敗,請重試。',
|
||
completeSaveError: '儲存完成狀態失敗,請重試。',
|
||
step: {
|
||
platform: '平台接入',
|
||
botConfig: '機器人配置',
|
||
aiEngine: 'AI 引擎',
|
||
done: '完成',
|
||
},
|
||
platform: {
|
||
title: '選擇平台',
|
||
description: '選擇機器人要接入的訊息平台。',
|
||
},
|
||
botConfig: {
|
||
title: '配置機器人',
|
||
description: '配置好機器人並確認其正常運作後再繼續。',
|
||
saveBot: '儲存並啟用',
|
||
resaveBot: '重新儲存配置',
|
||
botSaved: '機器人配置已儲存並啟用,請查看日誌確認連接正常。',
|
||
logsTitle: '機器人日誌',
|
||
logsDescription: '監控機器人活動,確認平台連接是否正常運作。',
|
||
},
|
||
aiEngine: {
|
||
title: '選擇 AI 引擎',
|
||
description: '選擇驅動機器人智慧的 AI 引擎。',
|
||
},
|
||
spaceBanner: {
|
||
message: '接入 LangBot Space,取得免費試用模型額度,零配置極速開箱!',
|
||
action: '前往授權登入',
|
||
},
|
||
config: {
|
||
botInfo: '機器人資訊',
|
||
botNamePlaceholder: '請輸入機器人名稱',
|
||
botDescPlaceholder: '請輸入機器人描述(可選)',
|
||
platformConfig: '{{platform}} 配置',
|
||
aiConfig: '{{engine}} 配置',
|
||
},
|
||
done: {
|
||
title: '一切就緒!',
|
||
description:
|
||
'機器人已建立並連接到 AI 流水線。你現在可以在工作台中管理它。',
|
||
backToWorkbench: '返回工作台',
|
||
},
|
||
},
|
||
addExtension: {
|
||
installTitle: '安裝{{type}}',
|
||
installConfirm: '確定要安裝{{type}}「{{name}}」嗎?',
|
||
installInfoType: '類型',
|
||
installInfoId: 'ID',
|
||
installInfoVersion: '版本',
|
||
installSuccess: '安裝成功',
|
||
installStage: {
|
||
mcpInstalling: '正在新增並連接 MCP 伺服器…',
|
||
skillInstalling: '正在安裝技能…',
|
||
installed: '完成',
|
||
},
|
||
manualAdd: '手動新增',
|
||
uploadExtension: '拖拽或點擊上傳擴充套件',
|
||
uploadHint: '支援 .zip(技能)和 .lbpkg(插件)檔案',
|
||
orContinueWith: '或選擇以下操作',
|
||
addMCPServerHint: '連接一個 MCP 工具伺服器擴充',
|
||
installFromGithub: '從 GitHub 安裝',
|
||
installFromGithubHint: '插件包或技能(SKILL.md)',
|
||
githubUrlHelp: '貼上 GitHub 地址',
|
||
githubUrlTooltip:
|
||
'插件:貼上倉庫、Release 或 Tag 地址。技能:貼上技能目錄裡的 SKILL.md 頁面地址。',
|
||
githubUrlPlaceholder: 'GitHub 倉庫、Release 或 SKILL.md 連結',
|
||
githubUrlRequired: '請輸入 GitHub 地址',
|
||
previewSkill: '預覽技能',
|
||
noSkillPreviewFound: '未找到可匯入的技能',
|
||
createSkill: '建立新的技能',
|
||
createSkillHint: '從本地目錄匯入或手動建立',
|
||
unsupportedFileType: '不支援的檔案類型,僅支援 .zip 和 .lbpkg 檔案',
|
||
},
|
||
errorPage: {
|
||
unexpectedError: '出錯了',
|
||
unexpectedErrorDescription: '發生了意外錯誤,請稍後重試。',
|
||
notFound: '頁面未找到',
|
||
notFoundDescription: '你訪問的頁面不存在或已被移動。',
|
||
backendUnavailableStatus: '後端服務不可用',
|
||
goBack: '返回上頁',
|
||
backToHome: '返回首頁',
|
||
backToLogin: '返回登入',
|
||
retrying: '正在重試',
|
||
retryFailed: '仍然無法連接後端,請確認服務已啟動後再重試。',
|
||
},
|
||
feishu: {
|
||
createApp: '一鍵建立飛書應用',
|
||
scanQRCode: '請使用飛書掃描以下 QR Code,授權後將自動建立應用並填寫憑證',
|
||
waitingForScan: '等待掃描中',
|
||
createSuccess: '應用建立成功!憑證已自動填入',
|
||
createFailed: '建立失敗',
|
||
connecting: '正在連線飛書服務...',
|
||
expired: 'QR Code 已過期,請重試',
|
||
denied: '使用者已拒絕授權',
|
||
connectionLost: '連線已斷開,請重試',
|
||
reconnecting: '正在重新連線...',
|
||
retry: '重試',
|
||
},
|
||
weixin: {
|
||
scanLogin: '掃碼登入微信',
|
||
scanQRCode: '請使用微信掃描以下 QR Code,授權後將自動登入並填寫令牌',
|
||
loginSuccess: '登入成功!令牌已自動填入',
|
||
loginFailed: '登入失敗',
|
||
},
|
||
dingtalk: {
|
||
createApp: '一鍵建立釘釘應用',
|
||
scanQRCode: '請使用釘釘掃描以下 QR Code,授權後將自動建立應用並填寫憑證',
|
||
waitingForScan: '等待掃碼中',
|
||
createSuccess: '應用建立成功!憑證已自動填入',
|
||
createFailed: '建立失敗',
|
||
connecting: '正在連線釘釘服務...',
|
||
retry: '重試',
|
||
robotCodeNote:
|
||
'機器人代碼無法自動取得,請前往釘釘開發者後台 > 機器人設定中手動複製。識圖、上傳檔案等功能需要填寫此欄位。',
|
||
},
|
||
wecombot: {
|
||
createBot: '一鍵建立企業微信機器人',
|
||
scanQRCode:
|
||
'請使用企業微信掃描以下 QR Code,授權後將自動建立機器人並填寫憑證',
|
||
waitingForScan: '等待掃碼中',
|
||
createSuccess: '機器人建立成功!憑證已自動填入',
|
||
createFailed: '建立失敗',
|
||
connecting: '正在連線企業微信服務...',
|
||
retry: '重試',
|
||
robotNameNote: '機器人名稱無法自動取得,請手動填寫。',
|
||
},
|
||
pluginPages: {
|
||
selectFromSidebar: '從側邊欄選擇一個插件頁面',
|
||
invalidPage: '無效的插件頁面',
|
||
},
|
||
skills: {
|
||
title: '技能',
|
||
description: '創建和管理可在對話中激活的技能',
|
||
createSkill: '創建技能',
|
||
createSkillDescription: '匯入本機目錄或手動填寫資訊建立',
|
||
editSkill: '編輯技能',
|
||
getSkillListError: '獲取技能列表失敗:',
|
||
skillName: '技能名稱',
|
||
displayName: '技能名稱',
|
||
displayNamePlaceholder: '顯示名稱',
|
||
skillSlug: '目錄名稱',
|
||
skillSlugPlaceholder: 'english-name-only',
|
||
skillSlugHelp: '用作技能目錄名,僅支援英文字母、數字、連字符和底線。',
|
||
skillDescription: '技能描述',
|
||
skillInstructions: '指令內容',
|
||
saveSuccess: '儲存成功',
|
||
saveError: '儲存失敗:',
|
||
createSuccess: '創建成功',
|
||
createError: '創建失敗:',
|
||
deleteSuccess: '刪除成功',
|
||
deleteError: '刪除失敗:',
|
||
deleteConfirmation: '你確定要刪除這個技能嗎?',
|
||
delete: '刪除技能',
|
||
skillNameRequired: '技能名稱不能為空',
|
||
skillDescriptionRequired: '技能描述不能為空',
|
||
packageRootRequired: '技能目錄不能為空',
|
||
scan: '掃描',
|
||
scanSuccess: '目錄掃描成功',
|
||
scanError: '掃描目錄失敗:',
|
||
noSkills: '暫未配置任何技能',
|
||
preview: '預覽',
|
||
previewInstructions: 'SKILL.md 內容預覽',
|
||
instructionsPlaceholder: '使用 Markdown 格式輸入技能指令...',
|
||
descriptionPlaceholder: '簡短描述此技能的功能',
|
||
packageRoot: '技能目錄',
|
||
packageRootHelp: '非必填。僅在導入已有技能目錄時需要填寫。',
|
||
importLocalDirectory: '匯入本地技能目錄',
|
||
chooseSkillDirectory: '選擇 SKILL.md 所在目錄',
|
||
chooseAnotherDirectory: '重新選擇目錄',
|
||
importingDirectory: '正在預覽...',
|
||
clearDirectoryPreview: '清除已選目錄',
|
||
noSkillMdInDirectory: '選擇的目錄中沒有找到 SKILL.md',
|
||
multipleSkillMdInDirectory:
|
||
'選擇的目錄中包含多個 SKILL.md,請直接選擇單個技能目錄。',
|
||
importDirectoryError: '匯入目錄失敗:',
|
||
advancedSettings: '進階設定',
|
||
searchSkills: '搜尋技能...',
|
||
selectSkills: '選擇技能',
|
||
builtin: '內建',
|
||
addSkill: '添加技能',
|
||
importFromGithub: '從 GitHub 安裝技能',
|
||
createManually: '手動創建',
|
||
uploadZip: '上傳 ZIP 包',
|
||
uploadZipOnly: '僅支援 .zip 技能包',
|
||
installSuccess: '技能安裝成功',
|
||
installError: '安裝技能失敗:',
|
||
enterRepoUrl: '輸入 GitHub 倉庫地址',
|
||
repoUrlPlaceholder: '例如 https://github.com/owner/repo',
|
||
fetchingReleases: '正在獲取發布版本...',
|
||
selectRelease: '選擇發布版本',
|
||
noReleasesFound: '未找到發布版本',
|
||
fetchReleasesError: '獲取發布版本失敗:',
|
||
selectAsset: '選擇要安裝的檔案',
|
||
sourceArchive: '源碼包 (zip)',
|
||
noAssetsFound: '此版本暫無可安裝的檔案',
|
||
fetchAssetsError: '獲取檔案列表失敗:',
|
||
backToReleases: '返回版本列表',
|
||
backToRepoUrl: '返回倉庫地址',
|
||
selectFromSidebar: '從側邊欄選擇一個技能',
|
||
backToAssets: '返回檔案列表',
|
||
releaseTag: '標籤:{{tag}}',
|
||
publishedAt: '發布時間:{{date}}',
|
||
prerelease: '預發布',
|
||
assetSize: '大小:{{size}}',
|
||
confirmInstall: '確認安裝',
|
||
installing: '正在安裝技能...',
|
||
loading: '載入中...',
|
||
previewLoadError: '載入預覽失敗',
|
||
dangerZone: '危險區域',
|
||
dangerZoneDescription: '不可逆且具破壞性的操作',
|
||
files: '檔案',
|
||
noFiles: '未找到檔案',
|
||
loadFilesError: '載入檔案失敗:',
|
||
readFileError: '讀取檔案失敗:',
|
||
saveFile: '儲存檔案',
|
||
saveFileSuccess: '檔案儲存成功',
|
||
saveFileError: '檔案儲存失敗:',
|
||
},
|
||
};
|
||
|
||
export default zhHant;
|