diff --git a/AGENTS.md b/AGENTS.md index dd006dc5..53172a78 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,6 +122,10 @@ CLI 只为「自己能权威解释的错误」发出语义化信号,服务端的 例外: 仅当作用域极小(≤3 行)且语义从上下文完全明确时,可使用 `k`/`v`(Object.entries 的 key/value)。 +### 6. 用户可见 CLI 文案必须支持中英文 + +新增或修改用户可见的 CLI 文案时必须同时提供 `en-US` / `zh-CN`;runtime 公共文案遵循同一规则,服务端错误仍按第 3 节原样透传。命令文案的具体检查项见 [command-add-remove.md](docs/agents/command-add-remove.md)。 + ## 完成改动后的快速验证 ```sh diff --git a/docs/agents/command-add-remove.md b/docs/agents/command-add-remove.md index 99d0fbee..bbea87b1 100644 --- a/docs/agents/command-add-remove.md +++ b/docs/agents/command-add-remove.md @@ -74,6 +74,7 @@ packages/commands/src/index.ts - 普通业务命令的 `run(ctx)` 只读 `ctx.flags` / `ctx.settings` / `ctx.client` - `commands/auth/**` 可用 `ctx.authStore`,`commands/config/**` 可用 `ctx.configStore`;不要把这些持久化能力扩散到普通业务命令 - `commands/plugin/**` 可用 `ctx.commandPacks`;产品 policy 由 runtime 绑定,命令不要自行 import 产品入口 +- [ ] 用户可见 Help 文案在命令文件中就近提供 `en-US` / `zh-CN`:命令 `description`、flag `description`、`notes` 和包含自然语言的 `exampleArgs`;纯命令语法示例可保留为字符串,服务端错误不翻译 - [ ] `packages/commands/src/index.ts`:新增或移除对应 export - [ ] 如果命令调用 Console Gateway,设置 `auth: "console"`;不要重复声明 console 凭证域 flags - [ ] 如果命令不需要网络或自己管理配置/登录,设置 `auth: "none"`;不要绕过 runtime auth stage diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 5df9cdf8..219e0122 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -4,10 +4,11 @@ import { commandPackPolicy } from "./command-pack-policy.ts"; import pkg from "../package.json" with { type: "json" }; const quickStartTasks = [ - "Help me generate a set of Amazon e-commerce main images for baseball caps (white background + lifestyle shots + model wear shots)", - "Help me generate a 3-minute humorous crosstalk audio clip", - "Help me generate a Little Red Riding Hood picture-book PDF (with illustrations)", - "Help me analyze this video and write a Xiaohongshu-style post", + "帮我创建一个能够生成短片分镜和视频的 Managed Agent。\n Help me create a Managed Agent that can generate short-film storyboards and videos.", + "生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。\n Generate an image of a cat in a spacesuit standing on Mars, then turn it into a video.", + "查看最近的模型用量、免费额度和限流情况。\n Check my recent model usage, free quota, and rate limits.", + "推荐一个适合图片理解和智能客服的模型。\n Recommend a model suitable for image understanding and intelligent customer service.", + "介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。\n Explain what Bailian CLI can help me accomplish, and recommend how to use it based on my needs.", ] as const; void createCli( diff --git a/packages/cli/tests/fixtures/command-pack/commands.mjs b/packages/cli/tests/fixtures/command-pack/commands.mjs index c6ba846b..698c1b39 100644 --- a/packages/cli/tests/fixtures/command-pack/commands.mjs +++ b/packages/cli/tests/fixtures/command-pack/commands.mjs @@ -1,5 +1,8 @@ const ping = { - description: "Ping the Command Pack fixture", + description: { + "en-US": "Ping the Command Pack fixture", + "zh-CN": "调用 Command Pack 测试命令", + }, auth: "none", flags: { message: { diff --git a/packages/commands/src/commands/advisor/recommend.ts b/packages/commands/src/commands/advisor/recommend.ts index c328b060..670db02a 100644 --- a/packages/commands/src/commands/advisor/recommend.ts +++ b/packages/commands/src/commands/advisor/recommend.ts @@ -226,24 +226,42 @@ function isEmptyResult(result: RecommendResult): boolean { } export default defineCommand({ - description: - "Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking)", + description: { + "en-US": + "Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking)", + "zh-CN": "为你的使用场景推荐最佳模型(意图分析 → 候选召回 → LLM 排序)", + }, auth: "apiKey", usageArgs: "--message [flags]", flags: { message: { type: "string", valueHint: "", - description: "Describe your requirements", + description: { "en-US": "Describe your requirements", "zh-CN": "描述你的需求" }, required: true, }, }, exampleArgs: [ - '--message "I need a visual-understanding chatbot"', - '--message "Build an Agent that auto-generates animations"', - '--message "Legal contract review, high precision required"', - '--message "Low-cost high-concurrency online customer service" --output text', - '--message "Long document summarization" --dry-run', + { + "en-US": '--message "I need a visual-understanding chatbot"', + "zh-CN": '--message "我需要一个能够理解图片的聊天机器人"', + }, + { + "en-US": '--message "Build an Agent that auto-generates animations"', + "zh-CN": '--message "构建一个可以自动生成动画的智能体"', + }, + { + "en-US": '--message "Legal contract review, high precision required"', + "zh-CN": '--message "审查法律合同,要求高准确率"', + }, + { + "en-US": '--message "Low-cost high-concurrency online customer service" --output text', + "zh-CN": '--message "低成本、高并发的在线客服" --output text', + }, + { + "en-US": '--message "Long document summarization" --dry-run', + "zh-CN": '--message "长文档摘要" --dry-run', + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/app/call.ts b/packages/commands/src/commands/app/call.ts index cdd2ef2c..5c9b0470 100644 --- a/packages/commands/src/commands/app/call.ts +++ b/packages/commands/src/commands/app/call.ts @@ -11,58 +11,111 @@ import { import { ansi, emitResult, emitBare } from "bailian-cli-runtime"; export default defineCommand({ - description: "Call a Bailian application (agent or workflow)", + description: { + "en-US": "Call a Bailian application (agent or workflow)", + "zh-CN": "调用百炼应用(智能体或工作流)", + }, auth: "apiKey", usageArgs: "--app-id --prompt [flags]", flags: { appId: { type: "string", valueHint: "", - description: "Application ID (required)", + description: { "en-US": "Application ID (required)", "zh-CN": "应用 ID(必填)" }, required: true, }, prompt: { type: "string", valueHint: "", - description: "Input prompt text", + description: { "en-US": "Input prompt text", "zh-CN": "输入提示词文本" }, required: true, }, image: { type: "array", valueHint: "", - description: "Image URL(s) to pass to the app (repeatable)", + description: { + "en-US": "Image URL(s) to pass to the app (repeatable)", + "zh-CN": "传给应用的图片 URL(可重复)", + }, }, fileId: { type: "array", valueHint: "", - description: "Pre-uploaded file ID(s) (repeatable)", + description: { + "en-US": "Pre-uploaded file ID(s) (repeatable)", + "zh-CN": "已上传的文件 ID(可重复)", + }, }, sessionId: { type: "string", valueHint: "", - description: "Session ID for multi-turn conversation", + description: { + "en-US": "Session ID for multi-turn conversation", + "zh-CN": "多轮对话的 Session ID", + }, + }, + stream: { + type: "switch", + description: { + "en-US": "Stream response (default: on in TTY)", + "zh-CN": "流式输出响应(TTY 中默认开启)", + }, }, - stream: { type: "switch", description: "Stream response (default: on in TTY)" }, pipelineIds: { type: "string", valueHint: "", - description: "Knowledge base pipeline IDs (comma-separated)", + description: { + "en-US": "Knowledge base pipeline IDs (comma-separated)", + "zh-CN": "知识库 Pipeline ID(以逗号分隔)", + }, + }, + memoryId: { + type: "string", + valueHint: "", + description: { + "en-US": "Memory ID for long-term memory", + "zh-CN": "长期记忆使用的 Memory ID", + }, }, - memoryId: { type: "string", valueHint: "", description: "Memory ID for long-term memory" }, bizParams: { type: "string", valueHint: "", - description: "Business parameters JSON (workflow variables)", + description: { + "en-US": "Business parameters JSON (workflow variables)", + "zh-CN": "业务参数 JSON(工作流变量)", + }, + }, + hasThoughts: { + type: "switch", + description: { "en-US": "Show agent thinking process", "zh-CN": "显示智能体思考过程" }, }, - hasThoughts: { type: "switch", description: "Show agent thinking process" }, }, exampleArgs: [ - '--app-id abc123 --prompt "Hello"', - '--app-id abc123 --prompt "Describe this image" --image https://example.com/photo.jpg', - '--app-id abc123 --prompt "Analyze the image" --image img1.jpg --image img2.jpg', - '--app-id abc123 --prompt "Continue" --session-id sess_xxx --stream', - '--app-id abc123 --prompt "Search for materials" --pipeline-ids pipe1,pipe2', - '--app-id abc123 --prompt "Start" --biz-params \'{"key":"value"}\'', + { + "en-US": '--app-id abc123 --prompt "Hello"', + "zh-CN": '--app-id abc123 --prompt "你好"', + }, + { + "en-US": + '--app-id abc123 --prompt "Describe this image" --image https://example.com/photo.jpg', + "zh-CN": '--app-id abc123 --prompt "描述这张图片" --image https://example.com/photo.jpg', + }, + { + "en-US": '--app-id abc123 --prompt "Analyze the image" --image img1.jpg --image img2.jpg', + "zh-CN": '--app-id abc123 --prompt "分析这些图片" --image img1.jpg --image img2.jpg', + }, + { + "en-US": '--app-id abc123 --prompt "Continue" --session-id sess_xxx --stream', + "zh-CN": '--app-id abc123 --prompt "继续" --session-id sess_xxx --stream', + }, + { + "en-US": '--app-id abc123 --prompt "Search for materials" --pipeline-ids pipe1,pipe2', + "zh-CN": '--app-id abc123 --prompt "搜索资料" --pipeline-ids pipe1,pipe2', + }, + { + "en-US": '--app-id abc123 --prompt "Start" --biz-params \'{"key":"value"}\'', + "zh-CN": '--app-id abc123 --prompt "开始" --biz-params \'{"key":"value"}\'', + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/app/list.ts b/packages/commands/src/commands/app/list.ts index dca7fe1f..f2d4b556 100644 --- a/packages/commands/src/commands/app/list.ts +++ b/packages/commands/src/commands/app/list.ts @@ -4,27 +4,38 @@ import { emitResult } from "bailian-cli-runtime"; const APP_LIST_API = "zeldaEasy.broadscope-bailian.app-control.list"; export default defineCommand({ - description: "List Bailian applications", + description: { "en-US": "List Bailian applications", "zh-CN": "列出百炼应用" }, auth: "console", usageArgs: "[flags]", flags: { name: { type: "string", valueHint: "", - description: "Filter by app name (keyword search)", + description: { + "en-US": "Filter by app name (keyword search)", + "zh-CN": "按应用名称筛选(关键词搜索)", + }, }, page: { type: "number", valueHint: "", - description: "Page number (default: 1)", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, }, pageSize: { type: "number", valueHint: "", - description: "Results per page (default: 30)", + description: { "en-US": "Results per page (default: 30)", "zh-CN": "每页结果数(默认:30)" }, }, }, - exampleArgs: ["", "--name customer service", "--page 2 --page-size 10", "--output json"], + exampleArgs: [ + "", + { + "en-US": "--name customer service", + "zh-CN": "--name 客户服务", + }, + "--page 2 --page-size 10", + "--output json", + ], async run(ctx) { const { settings, flags } = ctx; const name = flags.name || ""; diff --git a/packages/commands/src/commands/auth/generate-access-token.ts b/packages/commands/src/commands/auth/generate-access-token.ts index 08825563..fd95d072 100644 --- a/packages/commands/src/commands/auth/generate-access-token.ts +++ b/packages/commands/src/commands/auth/generate-access-token.ts @@ -10,24 +10,33 @@ const FLAGS = { accessKeyId: { type: "string", valueHint: "", - description: "Alibaba Cloud Access Key ID", + description: { "en-US": "Alibaba Cloud Access Key ID", "zh-CN": "阿里云 Access Key ID" }, required: true, }, accessKeySecret: { type: "string", valueHint: "", - description: "Alibaba Cloud Access Key Secret", + description: { + "en-US": "Alibaba Cloud Access Key Secret", + "zh-CN": "阿里云 Access Key Secret", + }, required: true, }, securityToken: { type: "string", valueHint: "", - description: "Alibaba Cloud STS Security Token to store (optional)", + description: { + "en-US": "Alibaba Cloud STS Security Token to store (optional)", + "zh-CN": "要保存的阿里云 STS Security Token(可选)", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "Generate a CLI access token using OpenAPI AK/SK", + description: { + "en-US": "Generate a CLI access token using OpenAPI AK/SK", + "zh-CN": "使用 OpenAPI AK/SK 生成 CLI Access Token", + }, auth: "none", usageArgs: "--access-key-id --access-key-secret --security-token ", flags: FLAGS, diff --git a/packages/commands/src/commands/auth/login.ts b/packages/commands/src/commands/auth/login.ts index 006dcd35..6c31780d 100644 --- a/packages/commands/src/commands/auth/login.ts +++ b/packages/commands/src/commands/auth/login.ts @@ -15,8 +15,11 @@ function hasValue(value: unknown): value is string { } export default defineCommand({ - description: - "Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist)", + description: { + "en-US": + "Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist)", + "zh-CN": "使用 API Key、控制台浏览器登录或 OpenAPI AK/SK 进行认证(多种凭证可共存)", + }, auth: "none", usageArgs: "--api-key | --console | --open-api --access-key-id --access-key-secret ", @@ -24,36 +27,54 @@ export default defineCommand({ apiKey: { type: "string", valueHint: "", - description: "Model API key to store", + description: { "en-US": "Model API key to store", "zh-CN": "要保存的模型 API Key" }, }, baseUrl: { type: "string", valueHint: "", - description: "Model API base URL (used with --api-key for validation)", + description: { + "en-US": "Model API base URL (used with --api-key for validation)", + "zh-CN": "模型 API Base URL(用于配合 --api-key 进行验证)", + }, }, console: { type: "switch", - description: - "Sign in via browser; use --console-site to choose domestic (default) or international", + description: { + "en-US": + "Sign in via browser; use --console-site to choose domestic (default) or international", + "zh-CN": "通过浏览器登录;使用 --console-site 选择国内站(默认)或国际站", + }, }, consoleSite: { type: "string", valueHint: "", - description: "Console site: domestic, international", + description: { + "en-US": "Console site: domestic, international", + "zh-CN": "控制台站点:domestic、international", + }, }, openApi: { type: "switch", - description: "Store Alibaba Cloud OpenAPI AK/SK credentials", + description: { + "en-US": "Store Alibaba Cloud OpenAPI AK/SK credentials", + "zh-CN": "保存阿里云 OpenAPI AK/SK 凭证", + }, }, accessKeyId: { type: "string", valueHint: "", - description: "Alibaba Cloud Access Key ID to store", + description: { + "en-US": "Alibaba Cloud Access Key ID to store", + "zh-CN": "要保存的阿里云 Access Key ID", + }, }, accessKeySecret: { type: "string", valueHint: "", - description: "Alibaba Cloud Access Key Secret to store", + description: { + "en-US": "Alibaba Cloud Access Key Secret to store", + "zh-CN": "要保存的阿里云 Access Key Secret", + }, }, }, exampleArgs: [ diff --git a/packages/commands/src/commands/auth/logout.ts b/packages/commands/src/commands/auth/logout.ts index 2a2860a0..9e69d167 100644 --- a/packages/commands/src/commands/auth/logout.ts +++ b/packages/commands/src/commands/auth/logout.ts @@ -2,17 +2,26 @@ import { defineCommand } from "bailian-cli-core"; import { emitBare } from "bailian-cli-runtime"; export default defineCommand({ - description: "Clear stored credentials; full logout also clears the model Base URL", + description: { + "en-US": "Clear stored credentials; full logout also clears the model Base URL", + "zh-CN": "清除已保存的凭证;完整退出还会清除模型 Base URL", + }, auth: "none", usageArgs: "[--console | --open-api] [--dry-run]", flags: { console: { type: "switch", - description: "Only clear the console access_token, keep api_key intact", + description: { + "en-US": "Only clear the console access_token, keep api_key intact", + "zh-CN": "仅清除控制台 access_token,保留 api_key", + }, }, openApi: { type: "switch", - description: "Only clear OpenAPI AK/SK/STS credentials, keep other credentials intact", + description: { + "en-US": "Only clear OpenAPI AK/SK/STS credentials, keep other credentials intact", + "zh-CN": "仅清除 OpenAPI AK/SK/STS 凭证,保留其他凭证", + }, }, }, exampleArgs: ["", "--console", "--open-api", "--dry-run"], diff --git a/packages/commands/src/commands/auth/status.ts b/packages/commands/src/commands/auth/status.ts index d15c6032..893d6554 100644 --- a/packages/commands/src/commands/auth/status.ts +++ b/packages/commands/src/commands/auth/status.ts @@ -3,7 +3,10 @@ import { emitResult, emitBare } from "bailian-cli-runtime"; import { API_KEY_PAGE } from "bailian-cli-runtime"; export default defineCommand({ - description: "Show current authentication state", + description: { + "en-US": "Show current authentication state", + "zh-CN": "显示当前认证状态", + }, auth: "none", exampleArgs: ["", "--output json"], async run(ctx) { diff --git a/packages/commands/src/commands/config/agent/index.ts b/packages/commands/src/commands/config/agent/index.ts index 54bb55cb..581f3cbe 100644 --- a/packages/commands/src/commands/config/agent/index.ts +++ b/packages/commands/src/commands/config/agent/index.ts @@ -9,54 +9,73 @@ const FLAGS = { agent: { type: "string", valueHint: "", - description: `Target agent: ${VALID_AGENT_NAMES.join(", ")}`, + description: { + "en-US": `Target agent: ${VALID_AGENT_NAMES.join(", ")}`, + "zh-CN": `目标 Agent:${VALID_AGENT_NAMES.join(", ")}`, + }, required: true, choices: VALID_AGENT_NAMES, }, baseUrl: { type: "string", valueHint: "", - description: "API base URL", + description: { "en-US": "API base URL", "zh-CN": "API Base URL" }, }, region: { type: "string", valueHint: "", - description: - "Model Studio region (e.g. cn-beijing, ap-southeast-1); converted into --base-url. Token Plan only", + description: { + "en-US": + "Model Studio region (e.g. cn-beijing, ap-southeast-1); converted into --base-url. Token Plan only", + "zh-CN": + "模型服务地域(例如 cn-beijing、ap-southeast-1);将转换为 --base-url。仅用于 Token Plan", + }, }, apiKey: { type: "string", valueHint: "", - description: "API key", + description: { "en-US": "API key", "zh-CN": "API Key" }, }, key: { type: "string", valueHint: "", - description: - 'Obfuscated API key from the web console (starts with "o1_"); decoded into --api-key', + description: { + "en-US": + 'Obfuscated API key from the web console (starts with "o1_"); decoded into --api-key', + "zh-CN": '来自 Web 控制台的混淆 API Key(以 "o1_" 开头);将解码为 --api-key', + }, }, model: { type: "string", valueHint: "", - description: "Default model name", + description: { "en-US": "Default model name", "zh-CN": "默认模型名称" }, required: true, }, contextWindow: { type: "number", valueHint: "", - description: "OpenClaw only: model context window in tokens (default: 256000)", + description: { + "en-US": "OpenClaw only: model context window in tokens (default: 256000)", + "zh-CN": "仅 OpenClaw:模型上下文窗口 Token 数(默认:256000)", + }, }, wireApi: { type: "string", valueHint: "", - description: - 'Codex only: wire protocol (default: responses). "chat" only works with legacy Codex <= 0.80.0', + description: { + "en-US": + 'Codex only: wire protocol (default: responses). "chat" only works with legacy Codex <= 0.80.0', + "zh-CN": '仅 Codex:通信协议(默认:responses)。"chat" 仅适用于旧版 Codex <= 0.80.0', + }, choices: ["chat", "responses"], }, } satisfies FlagsDef; export default defineCommand({ - description: "Configure a coding agent to use DashScope API", + description: { + "en-US": "Configure a coding agent to use DashScope API", + "zh-CN": "配置编程 Agent 使用 DashScope API", + }, auth: "none", usageArgs: "--agent (--base-url | --region ) (--api-key | --key ) --model ", diff --git a/packages/commands/src/commands/config/list.ts b/packages/commands/src/commands/config/list.ts index bff9694f..15ec6cb6 100644 --- a/packages/commands/src/commands/config/list.ts +++ b/packages/commands/src/commands/config/list.ts @@ -2,7 +2,10 @@ import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitBare, emitResult } from "bailian-cli-runtime"; export default defineCommand({ - description: "List config profiles and show the active profile", + description: { + "en-US": "List config profiles and show the active profile", + "zh-CN": "列出配置 Profile 并显示当前激活项", + }, auth: "none", exampleArgs: ["", "--output json"], async run(ctx) { diff --git a/packages/commands/src/commands/config/scenarios.ts b/packages/commands/src/commands/config/scenarios.ts index c4586903..8f2cb9ab 100644 --- a/packages/commands/src/commands/config/scenarios.ts +++ b/packages/commands/src/commands/config/scenarios.ts @@ -1,3 +1,5 @@ +import type { Language } from "bailian-cli-core"; + /** * Curated "Playground" scenarios surfaced in the config UI. * @@ -23,7 +25,7 @@ export interface Scenario { inputs?: ScenarioInput[]; } -export const SCENARIOS: Scenario[] = [ +export const SCENARIOS = [ // ---- 图像 ---- { id: "image-generate", @@ -150,17 +152,138 @@ export const SCENARIOS: Scenario[] = [ prompt: "为当前工作目录的项目生成一个结构清晰的 README.md,包含:项目简介、安装步骤、使用示例、目录结构说明。请先阅读现有代码与配置再撰写,内容必须与实际实现一致。", }, -]; +] as const satisfies readonly Scenario[]; + +type ScenarioTranslation = Pick; +type ScenarioId = (typeof SCENARIOS)[number]["id"]; + +const EN_US_SCENARIOS = { + "image-generate": { + title: "Text to image", + description: "Generate a sample image and save it to the output directory.", + category: "Image", + prompt: + "Use bl's image generation capability (such as `bl image generate`) to create a sample image: a corgi holding an umbrella in the rain, watercolor style, with soft lighting. Save it to the output directory and tell me the file path.", + }, + "image-describe": { + title: "Image understanding", + description: "Pick an image from the output directory and describe its content and style.", + category: "Image", + prompt: + "Pick an image from the output directory (output/images by default). Describe its subject, composition, colors, and style in detail, then suggest suitable use cases. If the directory is empty, say so.", + }, + "image-alt-batch": { + title: "Batch alt text", + description: "Generate accessible alt text for images in the output directory.", + category: "Image", + prompt: + "Scan all images in the output directory (output/images by default) and write concise, accurate accessibility alt text for each one. Summarize the results in a File name -> Alt text table. If the directory is empty, say so.", + }, + "image-to-code": { + title: "Screenshot to code", + description: "Recreate a UI screenshot from the output directory with HTML and CSS.", + category: "Image", + prompt: + "Find a UI screenshot in the output directory (output/images by default) and recreate its layout, spacing, and colors as closely as possible with HTML and CSS. Save it as a single file that opens directly in a browser and briefly explain your approach. If no screenshot is available, say so.", + }, + "speech-generate": { + title: "Text to speech", + description: "Turn a sample sentence into natural speech.", + category: "Audio", + prompt: + "Use bl's speech synthesis capability (such as a `bl speech` command) to turn this sentence into natural speech: Welcome to Alibaba Cloud Model Studio CLI, making multimodal creation easier. Save the audio to the output directory and tell me the file path.", + }, + "audio-summarize": { + title: "Transcribe and summarize audio", + description: "Transcribe an audio file from the output directory and summarize its key points.", + category: "Audio", + prompt: + "Find an audio file in the output directory (output/speech by default), transcribe it, provide the full transcript, and then summarize the key points as a list. If the directory is empty or transcription is unavailable, explain that and try to complete the task with the capabilities available.", + }, + "video-generate": { + title: "Text to video", + description: "Generate a sample short video.", + category: "Video", + prompt: + "Use bl's video generation capability (such as `bl video generate`) to create a sample short video: a teenager running along the beach at sunset, cinematic, in slow motion. Save it to the output directory and tell me the file path.", + }, + "video-storyboard": { + title: "Video storyboard", + description: "Create a storyboard suitable for text-to-video generation.", + category: "Video", + prompt: + "Create a storyboard for a 15-30 second short video themed The first cup of coffee in the city at dawn. For each shot, provide the visual description, duration, subtitles or narration, and an English prompt ready for text-to-video generation.", + }, + "media-prompt-craft": { + title: "Multimodal prompts", + description: "Expand a sample idea into image, video, and speech prompts.", + category: "Multimodal", + prompt: + "Expand the idea A night market in a futuristic cyber city into three high-quality generation prompts: 1) text to image, 2) text to video, and 3) speech style. Provide each prompt in both Chinese and English, with brief parameter recommendations.", + }, + "image-story-narration": { + title: "Image narration", + description: "Write narration for an image in the output directory.", + category: "Multimodal", + prompt: + "Pick an image from the output directory (output/images by default) and write an engaging English narration of about 60 seconds. Then provide a plain-text version ready for speech synthesis. If the directory is empty, say so.", + }, + "summarize-project": { + title: "Summarize this project", + description: + "Read the current directory and summarize its architecture, stack, and main modules.", + category: "Code", + prompt: + "Inspect the project structure and key source files in the current working directory, then concisely summarize: 1) what it does, 2) its technology stack, 3) its main modules and their responsibilities, and 4) notable design choices. Inspect the code before drawing conclusions; do not guess.", + }, + "write-tests": { + title: "Add tests for a core module", + description: "Choose an under-tested core module and add unit tests.", + category: "Code", + prompt: + "Choose a core module in the current project that has no tests or weak coverage. Add comprehensive unit tests for its main branches and edge cases, following the project's existing test framework and style. Read the relevant files and dependencies before writing tests.", + }, + "code-review": { + title: "Code review", + description: "Review core project code and identify concrete improvements.", + category: "Code", + prompt: + "Review the current project's core source code for potential bugs, security risks, performance issues, and maintainability problems. Give specific, actionable recommendations ordered by severity. Inspect the project structure and select the key files before reviewing them.", + }, + "explain-code": { + title: "Explain core code", + description: "Choose an entry point or core module and explain how it works.", + category: "Code", + prompt: + "Choose the current project's entry point or a core module and explain its responsibilities, key execution flow, and dependencies. Use clear English and include the call relationships when useful.", + }, + "generate-readme": { + title: "Generate README", + description: "Generate a clear README.md that matches the implementation.", + category: "Documentation", + prompt: + "Generate a clear README.md for the project in the current working directory. Include an overview, installation steps, usage examples, and a directory structure guide. Read the existing source code and configuration first; the content must match the actual implementation.", + }, +} satisfies Record; + +export function localizeScenarios(language: Language): Scenario[] { + if (language === "zh-CN") return SCENARIOS.map((scenario) => ({ ...scenario })); + + return SCENARIOS.map((scenario) => ({ + ...scenario, + ...EN_US_SCENARIOS[scenario.id], + })); +} /** Look up a scenario by id, or undefined when unknown. */ -export function getScenario(id: string): Scenario | undefined { - return SCENARIOS.find((s) => s.id === id); +export function getScenario(id: string, language: Language): Scenario | undefined { + return localizeScenarios(language).find((scenario) => scenario.id === id); } /** Fill a scenario's `{{placeholder}}` tokens from user-provided values. */ export function renderScenarioPrompt(scenario: Scenario, values: Record): string { return scenario.prompt.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => { - const v = values[key]; - return typeof v === "string" ? v.trim() : ""; + const value = values[key]; + return typeof value === "string" ? value.trim() : ""; }); } diff --git a/packages/commands/src/commands/config/set.ts b/packages/commands/src/commands/config/set.ts index ea75a4aa..5aa9e1dc 100644 --- a/packages/commands/src/commands/config/set.ts +++ b/packages/commands/src/commands/config/set.ts @@ -3,25 +3,30 @@ import { emitResult } from "bailian-cli-runtime"; import { SECRET_KEYS, resolveKey, validateAndCoerce } from "./shared.ts"; export default defineCommand({ - description: "Set a config value", + description: { "en-US": "Set a config value", "zh-CN": "设置配置项" }, auth: "none", usageArgs: "--key --value ", flags: { key: { type: "string", valueHint: "", - description: - "Config key (base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, security_token, default_*_model, workspace_id)", + description: { + "en-US": + "Config key (language, base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, security_token, default_*_model, workspace_id)", + "zh-CN": + "配置项名称(language、base_url、output、output_dir、timeout、api_key、access_token、access_key_id、access_key_secret、security_token、default_*_model、workspace_id)", + }, required: true, }, value: { type: "string", valueHint: "", - description: "Value to set", + description: { "en-US": "Value to set", "zh-CN": "要设置的值" }, required: true, }, }, exampleArgs: [ + "--key language --value zh-CN", "--key output --value json", "--key timeout --value 600", "--key base_url --value https://dashscope.aliyuncs.com", diff --git a/packages/commands/src/commands/config/shared.ts b/packages/commands/src/commands/config/shared.ts index 2473f80c..8cd5446f 100644 --- a/packages/commands/src/commands/config/shared.ts +++ b/packages/commands/src/commands/config/shared.ts @@ -1,7 +1,13 @@ -import { BailianError, ExitCode, normalizeModelBaseUrl } from "bailian-cli-core"; +import { + BailianError, + ExitCode, + normalizeModelBaseUrl, + SUPPORTED_LANGUAGES, +} from "bailian-cli-core"; /** Config keys that `config set` / `config ui` accept for read/write. */ export const VALID_KEYS = [ + "language", "base_url", "output", "output_dir", @@ -47,6 +53,7 @@ export const UI_VALID_KEYS = [...VALID_KEYS, ...UI_EXTRA_KEYS] as const; // Keys the UI renders as a fixed-choice dropdown instead of a free-text input. export const UI_ENUM_KEYS: Record = { + language: [...SUPPORTED_LANGUAGES], output: ["text", "json"], console_site: ["domestic", "international"], }; @@ -144,6 +151,13 @@ export function validateAndCoerce(key: string, value: string): string | number { ); } + if (resolvedKey === "language" && !(SUPPORTED_LANGUAGES as readonly string[]).includes(value)) { + throw new BailianError( + `Invalid language "${value}". Valid values: ${SUPPORTED_LANGUAGES.join(", ")}`, + ExitCode.USAGE, + ); + } + if (resolvedKey === "output" && !["text", "json"].includes(value)) { throw new BailianError( `Invalid output format "${value}". Valid values: text, json`, diff --git a/packages/commands/src/commands/config/show.ts b/packages/commands/src/commands/config/show.ts index ee6e3fa7..49ca83d2 100644 --- a/packages/commands/src/commands/config/show.ts +++ b/packages/commands/src/commands/config/show.ts @@ -1,9 +1,9 @@ -import { defineCommand, detectOutputFormat, maskToken } from "bailian-cli-core"; +import { DEFAULT_LANGUAGE, defineCommand, detectOutputFormat, maskToken } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { SECRET_KEYS } from "./shared.ts"; export default defineCommand({ - description: "Display current configuration", + description: { "en-US": "Display current configuration", "zh-CN": "显示当前配置" }, auth: "none", exampleArgs: ["", "--output json"], async run(ctx) { @@ -14,6 +14,7 @@ export default defineCommand({ const result: Record = { ...file, + language: file.language ?? DEFAULT_LANGUAGE, base_url: client.baseUrl, output: settings.output, timeout: settings.timeout, diff --git a/packages/commands/src/commands/config/ui-html.ts b/packages/commands/src/commands/config/ui-html.ts index 0c7ebbaf..0b7c27a4 100644 --- a/packages/commands/src/commands/config/ui-html.ts +++ b/packages/commands/src/commands/config/ui-html.ts @@ -4,7 +4,10 @@ // fetches carry the session token from the page URL. Visual language mirrors // the bailian landing design system (Inter / Geist Mono, gradient accents, // lift-on-hover cards). -export const PAGE_HTML = ` +import type { Language } from "bailian-cli-core"; +import { renderConfigUiShell } from "./ui-i18n.ts"; + +const PAGE_HTML = ` @@ -30,6 +33,7 @@ export const PAGE_HTML = ` --t-lift: .3s cubic-bezier(.2,.7,.2,1); --t-fast: .15s ease; } * { box-sizing: border-box; } + html:not(.i18n-ready) body { visibility: hidden; } body { margin: 0; font-family: var(--font); font-size: 14px; line-height: 1.5; color: var(--ink); background: #fafafc; -webkit-font-smoothing: antialiased; } #app { display: flex; min-height: 100vh; } @@ -694,6 +698,8 @@ export const PAGE_HTML = ` var token = new URLSearchParams(location.search).get('token') || ''; var KEYS = [], SECRETS = [], ENUMS = {}, BOOLEANS = [], FIELD_DEFAULTS = {}, MODEL_CATALOG = {}, DATA = { default: {}, named: {} }, CURRENT = '', ACTIVE = 'default'; var loaded = { skills: false, mcp: false, agents: false, assets: false, playground: false, start: false }; + var UI_LANGUAGE = '__BL_CONFIG_UI_LANGUAGE__'; + var UI_TRANSLATIONS = __BL_CONFIG_UI_TRANSLATIONS__; var SCENARIOS = [], BUILTIN_SCENARIOS = [], DISPATCH_AGENTS = [], SCN_Q = '', SCN_FILTER = 'all', SCN_PAGE = 1, DISPATCH_SCN = null; var ASSETS = [], ASSET_FILTER = 'all', ASSET_SORT = 'new', ASSET_Q = '', ASSET_PAGE = 1; var AGENTS = [], AGENT_FILTER = 'all', AGENT_Q = '', AGENT_PAGE = 1; @@ -717,6 +723,67 @@ export const PAGE_HTML = ` // Signed-in avatar photo. The generated colour + person icon stay as the // background fallback while the image loads or if it fails. var AVATAR_URL = 'https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg'; + var UI_STATIC_COPY = []; + var UI_DOCUMENT_TITLE = document.title; + function translateUiText(value) { + var text = value == null ? '' : String(value); + if (UI_LANGUAGE !== 'zh-CN') return text; + for (var translationIndex = 0; translationIndex < UI_TRANSLATIONS.length; translationIndex++) { + var pair = UI_TRANSLATIONS[translationIndex]; + text = text.split(pair[0]).join(pair[1]); + } + return text; + } + function captureStaticUiCopy() { + var root = document.getElementById('app'); + function visit(node) { + if (node.nodeType === 3) { + if (node.nodeValue && node.nodeValue.trim()) UI_STATIC_COPY.push({ node: node, text: node.nodeValue }); + return; + } + if (node.nodeType !== 1) return; + var tag = node.tagName; + if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'CODE' || tag === 'PRE') return; + ['title', 'placeholder', 'aria-label', 'alt'].forEach(function (name) { + if (node.hasAttribute(name)) UI_STATIC_COPY.push({ node: node, attr: name, text: node.getAttribute(name) }); + }); + Array.prototype.forEach.call(node.childNodes, visit); + } + visit(root); + } + function applyStaticUiCopy() { + document.documentElement.lang = UI_LANGUAGE; + document.title = translateUiText(UI_DOCUMENT_TITLE); + UI_STATIC_COPY.forEach(function (entry) { + var value = translateUiText(entry.text); + if (entry.attr) entry.node.setAttribute(entry.attr, value); + else entry.node.nodeValue = value; + }); + document.documentElement.classList.add('i18n-ready'); + } + function applyUiLanguage(language) { + if (language !== 'en-US' && language !== 'zh-CN') return; + UI_LANGUAGE = language; + SCN_FILTER = 'all'; + SCN_PAGE = 1; + applyStaticUiCopy(); + renderProfiles(); + renderForm(); + if (loaded.start) loadQuickStart(); + if (loaded.skills) renderSkills(); + if (loaded.mcp) renderMcp(); + if (loaded.agents) { renderAgentFilters(); renderAgents(); } + if (loaded.assets) { renderAssetFilters(); renderAssets(); } + if (loaded.playground) { loaded.playground = false; loadPlayground(); } + if (AUTH) renderAccount(AUTH); + var sidebar = document.getElementById('sidebar'); + var sidebarToggle = document.getElementById('sidebarToggle'); + if (sidebar && sidebarToggle) { + var sidebarLabel = sidebar.classList.contains('is-collapsed') ? 'Expand sidebar' : 'Collapse sidebar'; + sidebarToggle.setAttribute('aria-label', translateUiText(sidebarLabel)); + sidebarToggle.title = translateUiText(sidebarLabel); + } + } function setAvatar(elm, seed) { elm.style.background = avatarStyle(seed); elm.innerHTML = PERSON_SVG; @@ -736,13 +803,16 @@ export const PAGE_HTML = ` if (text !== undefined && text !== null) e.textContent = text; return e; } + function uiEl(tag, cls, text) { + return el(tag, cls, translateUiText(text)); + } function originBadge(origin) { var o = origin === 'remote' ? 'remote' : 'local'; - return el('span', 'origin ' + o, o === 'remote' ? 'Remote' : 'Local'); + return el('span', 'origin ' + o, translateUiText(o === 'remote' ? 'Remote' : 'Local')); } function setStatus(msg, isErr) { var s = document.getElementById('status'); - s.textContent = msg || ''; + s.textContent = translateUiText(msg || ''); s.className = isErr ? 'err' : 'muted'; } function setCount(view, n) { @@ -786,18 +856,18 @@ export const PAGE_HTML = ` renderQuickStart(HEALTH, auth, agents); }).catch(function (e) { renderError(body, e); }); } - function qsDesc(html) { var p = el('p', 'qs-desc'); p.innerHTML = html; return p; } + function qsDesc(html) { var p = el('p', 'qs-desc'); p.innerHTML = translateUiText(html); return p; } function qsBtn(label, onclick, primary) { - var b = el('button', primary ? 'btn-primary' : 'btn-soft', label); + var b = el('button', primary ? 'btn-primary' : 'btn-soft', translateUiText(label)); b.type = 'button'; b.onclick = onclick; return b; } function qsStep(n, done, locked, title, descNode, actNode) { var step = el('div', 'qs-step' + (done ? ' done' : '') + (locked ? ' locked' : '')); var head = el('div', 'qs-head'); head.appendChild(el('span', 'qs-ic', done ? '\u2713' : String(n))); - if (done) head.appendChild(el('span', 'qs-done-tag', 'Done')); + if (done) head.appendChild(el('span', 'qs-done-tag', translateUiText('Done'))); step.appendChild(head); - step.appendChild(el('div', 'qs-title', title)); + step.appendChild(el('div', 'qs-title', translateUiText(title))); if (descNode) step.appendChild(descNode); if (actNode && !done) { var wrap = el('div', 'qs-act'); wrap.appendChild(actNode); step.appendChild(wrap); } return step; @@ -884,7 +954,7 @@ export const PAGE_HTML = ` t.onclick = function () { CURRENT = name; renderProfiles(); renderForm(); setStatus(''); openDrawer(); }; grid.appendChild(t); }); - var add = el('div', 'tile tile-add', '+ New profile'); + var add = uiEl('div', 'tile tile-add', '+ New profile'); add.onclick = newProfile; grid.appendChild(add); } @@ -913,7 +983,7 @@ export const PAGE_HTML = ` } function detailSection(label, node, action) { var sec = el('div', 'detail-sec'); - var head = el('div', 'detail-label', label); + var head = el('div', 'detail-label', translateUiText(label)); if (action) { head.classList.add('detail-label-row'); head.appendChild(action); } sec.appendChild(head); sec.appendChild(node); @@ -1022,7 +1092,7 @@ export const PAGE_HTML = ` return html; } function renderMarkdownInto(container, md) { - if (!md) { container.appendChild(el('div', 'loading', '(empty)')); return; } + if (!md) { container.appendChild(uiEl('div', 'loading', '(empty)')); return; } var wrap = el('div', 'md-body'); wrap.innerHTML = renderMarkdown(md); container.appendChild(wrap); @@ -1039,10 +1109,10 @@ export const PAGE_HTML = ` { source: 'windsurf', label: 'Windsurf' }, { source: 'gemini', label: 'Gemini' } ]; - function setSkillErr(msg) { var e = document.getElementById('skillErr'); if (e) e.textContent = msg || ''; } + function setSkillErr(msg) { var e = document.getElementById('skillErr'); if (e) e.textContent = translateUiText(msg || ''); } function openSkillInstall() { var title = document.getElementById('infoTitle'); - title.textContent = ''; title.appendChild(el('span', '', 'Add skill')); + title.textContent = ''; title.appendChild(uiEl('span', '', 'Add skill')); var body = document.getElementById('infoBody'); body.innerHTML = ''; var sel = el('select', 'select'); sel.id = 'skillNewSource'; SKILL_TARGETS.forEach(function (s) { var o = el('option', '', s.label); o.value = s.source; sel.appendChild(o); }); @@ -1053,21 +1123,21 @@ export const PAGE_HTML = ` var fileInp = el('input'); fileInp.id = 'skillFile'; fileInp.type = 'file'; fileInp.accept = '.zip,application/zip'; fileInp.style.display = 'none'; var picker = el('div', 'file-picker'); - var pickBtn = el('button', 'file-pick-btn', 'Choose .zip file'); pickBtn.type = 'button'; - var fileName = el('span', 'file-name', 'No file selected'); + var pickBtn = uiEl('button', 'file-pick-btn', 'Choose .zip file'); pickBtn.type = 'button'; + var fileName = uiEl('span', 'file-name', 'No file selected'); pickBtn.onclick = function () { fileInp.click(); }; fileInp.onchange = function () { var f = fileInp.files && fileInp.files[0]; - fileName.textContent = f ? f.name : 'No file selected'; + fileName.textContent = f ? f.name : translateUiText('No file selected'); fileName.classList.toggle('has-file', !!f); }; picker.appendChild(pickBtn); picker.appendChild(fileName); picker.appendChild(fileInp); body.appendChild(detailSection('Skill package (.zip)', picker)); - body.appendChild(el('p', 'mcp-note', 'The .zip must contain a SKILL.md at its root or inside a single top-level folder.')); + body.appendChild(uiEl('p', 'mcp-note', 'The .zip must contain a SKILL.md at its root or inside a single top-level folder.')); var err = el('div', 'modal-err'); err.id = 'skillErr'; body.appendChild(err); openInfoDrawer(); var foot = document.getElementById('infoFoot'); foot.innerHTML = ''; foot.hidden = false; - var install = el('button', 'btn-primary', 'Install'); install.type = 'button'; + var install = uiEl('button', 'btn-primary', 'Install'); install.type = 'button'; install.onclick = function () { doInstallSkill(install); }; foot.appendChild(install); } @@ -1106,7 +1176,7 @@ export const PAGE_HTML = ` chips.appendChild(el('span', 'chip', s.fileCount + ' files')); body.appendChild(detailSection('Installed in', chips)); body.appendChild(detailSection('Path', el('div', 'detail-path', s.path))); - var codeSec = detailSection('SKILL.md', el('div', 'loading', 'Loading…')); + var codeSec = detailSection('SKILL.md', uiEl('div', 'loading', 'Loading…')); body.appendChild(codeSec); openInfoDrawer(); api('/api/skill?id=' + encodeURIComponent(s.id)).then(function (r) { return r.json(); }).then(function (j) { @@ -1114,14 +1184,14 @@ export const PAGE_HTML = ` renderMarkdownInto(codeSec, (j && j.content) || ''); }).catch(function (e) { codeSec.removeChild(codeSec.lastChild); - codeSec.appendChild(el('div', 'err', 'Failed to load: ' + e)); + codeSec.appendChild(uiEl('div', 'err', 'Failed to load: ' + e)); }); } function makeSelect(key, options, current) { var sel = document.createElement('select'); sel.id = 'f_' + key; sel.name = key; sel.className = 'select'; - var blank = document.createElement('option'); blank.value = ''; blank.textContent = '(unset)'; + var blank = document.createElement('option'); blank.value = ''; blank.textContent = translateUiText('(unset)'); sel.appendChild(blank); options.forEach(function (opt) { var o = document.createElement('option'); o.value = opt; o.textContent = opt; @@ -1134,12 +1204,12 @@ export const PAGE_HTML = ` function renderForm() { var form = document.getElementById('form'); form.innerHTML = ''; - document.getElementById('currentName').textContent = CURRENT === '' ? 'default (top-level)' : CURRENT; + document.getElementById('currentName').textContent = CURRENT === '' ? translateUiText('default (top-level)') : CURRENT; document.getElementById('deleteBtn').style.display = CURRENT === '' ? 'none' : ''; var selectedName = CURRENT === '' ? 'default' : CURRENT; var useBtn = document.getElementById('useBtn'); useBtn.disabled = selectedName === ACTIVE; - useBtn.textContent = selectedName === ACTIVE ? 'Active' : 'Save & Activate'; + useBtn.textContent = translateUiText(selectedName === ACTIVE ? 'Active' : 'Save & Activate'); var data = profileData(CURRENT); KEYS.forEach(function (key) { var row = el('div', 'row'); @@ -1154,10 +1224,10 @@ export const PAGE_HTML = ` // the key). 'new-password' reliably suppresses saved-credential autofill. input.autocomplete = 'new-password'; input.setAttribute('autocorrect', 'off'); input.spellcheck = false; - var toggle = el('button', 'toggle', 'show'); toggle.type = 'button'; + var toggle = el('button', 'toggle', translateUiText('show')); toggle.type = 'button'; toggle.onclick = function () { - if (input.type === 'password') { input.type = 'text'; toggle.textContent = 'hide'; } - else { input.type = 'password'; toggle.textContent = 'show'; } + if (input.type === 'password') { input.type = 'text'; toggle.textContent = translateUiText('hide'); } + else { input.type = 'password'; toggle.textContent = translateUiText('show'); } }; var wrap = el('div', 'inputwrap'); wrap.appendChild(input); wrap.appendChild(toggle); row.appendChild(label); row.appendChild(wrap); @@ -1190,7 +1260,7 @@ export const PAGE_HTML = ` function modelCatalogBlock(key, input) { var opts = MODEL_CATALOG[key] || []; var box = el('div', 'model-cat'); - box.appendChild(el('div', 'model-cat-hint', 'Available ' + modelCatLabel(key) + ' models · click to use')); + box.appendChild(el('div', 'model-cat-hint', translateUiText('Available ' + modelCatLabel(key) + ' models · click to use'))); var chips = el('div', 'model-chips'); function mark() { var cur = input.value.trim(); @@ -1211,7 +1281,7 @@ export const PAGE_HTML = ` var note = modelCatNote(key); if (note) { var noteEl = el('div', 'model-cat-note'); - noteEl.innerHTML = note; + noteEl.innerHTML = translateUiText(note); box.appendChild(noteEl); } setTimeout(mark, 0); @@ -1235,6 +1305,9 @@ export const PAGE_HTML = ` if (!result.ok) throw new Error((result.json && result.json.error) || 'error'); var saved = result.json.saved || {}; if (name === '') DATA.default = saved; else DATA.named[name] = saved; + if (result.json.uiLanguage && result.json.uiLanguage !== UI_LANGUAGE) { + applyUiLanguage(result.json.uiLanguage); + } return saved; }); } @@ -1259,17 +1332,17 @@ export const PAGE_HTML = ` } function setModalErr(msg) { - document.getElementById('modalErr').textContent = msg || ''; + document.getElementById('modalErr').textContent = translateUiText(msg || ''); } var _confirmOnOk = null; function openConfirm(opts) { opts = opts || {}; - document.getElementById('confirmTitle').textContent = opts.title || 'Are you sure?'; - document.getElementById('confirmMsg').textContent = opts.message || ''; + document.getElementById('confirmTitle').textContent = translateUiText(opts.title || 'Are you sure?'); + document.getElementById('confirmMsg').textContent = translateUiText(opts.message || ''); var ok = document.getElementById('confirmOk'); var cancel = document.getElementById('confirmCancel'); - ok.textContent = opts.okLabel || 'Delete'; + ok.textContent = translateUiText(opts.okLabel || 'Delete'); ok.className = opts.danger === false ? 'btn-primary' : 'btn-danger-solid'; cancel.hidden = !!opts.hideCancel; _confirmOnOk = opts.onConfirm || null; @@ -1322,6 +1395,9 @@ export const PAGE_HTML = ` }).then(function (result) { if (!result.ok) throw new Error('Saved, but activation failed: ' + ((result.json && result.json.error) || 'error')); ACTIVE = result.json.activeProfile || 'default'; + if (result.json.uiLanguage && result.json.uiLanguage !== UI_LANGUAGE) { + applyUiLanguage(result.json.uiLanguage); + } renderProfiles(); renderForm(); setStatus('Saved and activated.'); }).catch(function (err) { setStatus(err.message || String(err), true); }); } @@ -1350,8 +1426,8 @@ export const PAGE_HTML = ` function renderEmpty(container, msg, hint) { container.innerHTML = ''; var box = el('div', 'empty'); - box.appendChild(el('div', '', msg)); - if (hint) { var p = el('p', 'muted'); p.style.marginTop = '10px'; p.innerHTML = hint; box.appendChild(p); } + box.appendChild(el('div', '', translateUiText(msg))); + if (hint) { var p = el('p', 'muted'); p.style.marginTop = '10px'; p.innerHTML = translateUiText(hint); box.appendChild(p); } container.appendChild(box); } function renderError(container, e) { renderEmpty(container, 'Failed to load: ' + e); } @@ -1406,7 +1482,7 @@ export const PAGE_HTML = ` } }); bar.appendChild(navBtn('\u203a', info.page + 1, info.page >= info.pages)); - var sel = el('select', 'pg-size'); sel.title = 'Items per page'; + var sel = el('select', 'pg-size'); sel.title = translateUiText('Items per page'); PAGE_SIZES.forEach(function (s) { var o = el('option', '', s + ' / page'); o.value = String(s); if (s === getPageSize(view)) o.selected = true; @@ -1461,7 +1537,7 @@ export const PAGE_HTML = ` if (s.description) t.appendChild(el('p', 'tile-desc', s.description)); var srcRow = el('div', 'tile-foot'); (s.sources || []).forEach(function (src) { srcRow.appendChild(el('span', 'chip blue', src)); }); - srcRow.appendChild(el('span', 'chip', s.fileCount + ' files')); + srcRow.appendChild(uiEl('span', 'chip', s.fileCount + ' files')); t.appendChild(srcRow); grid.appendChild(t); }); @@ -1537,16 +1613,16 @@ export const PAGE_HTML = ` body.appendChild(detailSection('Configuration' + (editable ? ' (editable)' : ''), block, copyButton(function () { return editable ? document.getElementById('mcpEdit').value : json; }))); - if (!editable) body.appendChild(el('p', 'mcp-note', 'This source is read-only here (its config is TOML). Edit it directly in ' + (m.source || 'its config file') + '.')); + if (!editable) body.appendChild(uiEl('p', 'mcp-note', 'This source is read-only here (its config is TOML). Edit it directly in ' + (m.source || 'its config file') + '.')); var err = el('div', 'modal-err'); err.id = 'mcpErr'; body.appendChild(err); openInfoDrawer(); if (editable) { var foot = document.getElementById('infoFoot'); foot.innerHTML = ''; foot.hidden = false; - var del = el('button', 'btn-danger', 'Delete'); del.type = 'button'; + var del = uiEl('button', 'btn-danger', 'Delete'); del.type = 'button'; del.onclick = function () { openConfirm({ title: 'Delete MCP server?', message: 'Remove "' + m.name + '" from ' + m.source + '? This rewrites the source config file.', onConfirm: function () { doDeleteMcp(m); } }); }; - var save = el('button', 'btn-primary', 'Save'); save.type = 'button'; + var save = uiEl('button', 'btn-primary', 'Save'); save.type = 'button'; save.onclick = function () { doSaveMcp(m, save); }; foot.appendChild(del); foot.appendChild(save); } @@ -1564,16 +1640,16 @@ export const PAGE_HTML = ` { id: 'qoderwork', label: 'QoderWork' } ]; function copyButton(getText) { - var b = el('button', 'copy-btn', 'Copy'); b.type = 'button'; + var b = uiEl('button', 'copy-btn', 'Copy'); b.type = 'button'; b.onclick = function () { var text = getText(); - var done = function () { b.classList.add('copied'); b.textContent = 'Copied'; setTimeout(function () { b.classList.remove('copied'); b.textContent = 'Copy'; }, 1500); }; + var done = function () { b.classList.add('copied'); b.textContent = translateUiText('Copied'); setTimeout(function () { b.classList.remove('copied'); b.textContent = translateUiText('Copy'); }, 1500); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(done).catch(function () { fallbackCopy(text); done(); }); } else { fallbackCopy(text); done(); } }; return b; } - function setMcpErr(msg) { var e = document.getElementById('mcpErr'); if (e) e.textContent = msg || ''; } + function setMcpErr(msg) { var e = document.getElementById('mcpErr'); if (e) e.textContent = translateUiText(msg || ''); } function parseMcpEditor() { var parsed = JSON.parse(document.getElementById('mcpEdit').value); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('Config must be a JSON object.'); @@ -1605,7 +1681,7 @@ export const PAGE_HTML = ` } function openMcpCreate() { var title = document.getElementById('infoTitle'); - title.textContent = ''; title.appendChild(el('span', '', 'New MCP server')); + title.textContent = ''; title.appendChild(uiEl('span', '', 'New MCP server')); var body = document.getElementById('infoBody'); body.innerHTML = ''; var sel = el('select', 'select'); sel.id = 'mcpNewSource'; MCP_SOURCES.forEach(function (s) { var o = el('option', '', s.label); o.value = s.id; sel.appendChild(o); }); @@ -1618,7 +1694,7 @@ export const PAGE_HTML = ` var err = el('div', 'modal-err'); err.id = 'mcpErr'; body.appendChild(err); openInfoDrawer(); var foot = document.getElementById('infoFoot'); foot.innerHTML = ''; foot.hidden = false; - var create = el('button', 'btn-primary', 'Create'); create.type = 'button'; + var create = uiEl('button', 'btn-primary', 'Create'); create.type = 'button'; create.onclick = function () { doCreateMcp(create); }; foot.appendChild(create); } @@ -1664,7 +1740,7 @@ export const PAGE_HTML = ` var labels = { all: 'All', local: 'Local', remote: 'Remote' }; ['all', 'local', 'remote'].forEach(function (cat) { var b = el('button', 'filter' + (cat === AGENT_FILTER ? ' is-active' : '')); - b.appendChild(el('span', '', labels[cat])); + b.appendChild(uiEl('span', '', labels[cat])); b.appendChild(el('span', 'n', String(counts[cat] || 0))); b.onclick = function () { AGENT_FILTER = cat; AGENT_PAGE = 1; renderAgentFilters(); renderAgents(); }; bar.appendChild(b); @@ -1701,9 +1777,9 @@ export const PAGE_HTML = ` var meta = el('div', 'tile-meta'); meta.appendChild(originBadge(a.origin)); var pill; - if (a.installed && a.configured) pill = el('span', 'pill ok', 'Connected'); - else if (a.installed) pill = el('span', 'pill neutral', 'Installed'); - else pill = el('span', 'pill off', 'Not installed'); + if (a.installed && a.configured) pill = uiEl('span', 'pill ok', 'Connected'); + else if (a.installed) pill = uiEl('span', 'pill neutral', 'Installed'); + else pill = uiEl('span', 'pill off', 'Not installed'); meta.appendChild(pill); t.appendChild(meta); var foot = el('div', 'tile-foot'); @@ -1718,17 +1794,17 @@ export const PAGE_HTML = ` var actions = el('div', 'tile-actions'); var launch = el('button', 'icon-run'); launch.innerHTML = PLAY_SVG; - launch.setAttribute('aria-label', 'Quick launch'); + launch.setAttribute('aria-label', translateUiText('Quick launch')); var connected = a.installed && a.configured; var st = el('span', 'launch-status'); if (connected && a.launchable) { - launch.title = 'Open a new terminal and start this agent'; + launch.title = translateUiText('Open a new terminal and start this agent'); launch.onclick = function (e) { e.stopPropagation(); launchAgentCli(a, launch, st); }; } else { launch.disabled = true; - if (!a.installed) launch.title = 'Install this agent before launching'; - else if (!connected) launch.title = 'Connect this agent to bailian-cli before launching'; - else launch.title = 'The CLI for this agent was not found on your PATH — install it before launching'; + if (!a.installed) launch.title = translateUiText('Install this agent before launching'); + else if (!connected) launch.title = translateUiText('Connect this agent to bailian-cli before launching'); + else launch.title = translateUiText('The CLI for this agent was not found on your PATH — install it before launching'); } actions.appendChild(st); actions.appendChild(launch); @@ -1741,15 +1817,15 @@ export const PAGE_HTML = ` function launchAgentCli(a, btn, st) { btn.disabled = true; - st.textContent = 'Launching…'; st.className = 'launch-status'; + st.textContent = translateUiText('Launching…'); st.className = 'launch-status'; api('/api/agent/launch?id=' + encodeURIComponent(a.id), { method: 'POST' }) .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); }) .then(function (res) { btn.disabled = false; - if (!res.ok) { st.textContent = (res.j && res.j.error) || 'Launch failed'; st.className = 'launch-status err'; return; } - st.textContent = 'Launched → ' + (res.j.command || a.id); st.className = 'launch-status ok'; + if (!res.ok) { st.textContent = (res.j && res.j.error) || translateUiText('Launch failed'); st.className = 'launch-status err'; return; } + st.textContent = translateUiText('Launched → ') + (res.j.command || a.id); st.className = 'launch-status ok'; }) - .catch(function (e) { btn.disabled = false; st.textContent = 'Launch failed: ' + e; st.className = 'launch-status err'; }); + .catch(function (e) { btn.disabled = false; st.textContent = translateUiText('Launch failed: ') + e; st.className = 'launch-status err'; }); } function openAgentDetail(a) { @@ -1770,9 +1846,9 @@ export const PAGE_HTML = ` var chips = el('div', 'detail-chips'); chips.appendChild(el('span', 'chip', d.id)); var pill; - if (d.installed && d.configured) pill = el('span', 'pill ok', 'Connected'); - else if (d.installed) pill = el('span', 'pill neutral', 'Installed'); - else pill = el('span', 'pill off', 'Not installed'); + if (d.installed && d.configured) pill = uiEl('span', 'pill ok', 'Connected'); + else if (d.installed) pill = uiEl('span', 'pill neutral', 'Installed'); + else pill = uiEl('span', 'pill off', 'Not installed'); chips.appendChild(pill); body.appendChild(detailSection('Status', chips)); if (d.fields && d.fields.length) { @@ -1784,13 +1860,13 @@ export const PAGE_HTML = ` row.appendChild(val); if (f.secret && f.raw) { var shown = false; - var btn = el('button', 'kv-reveal', 'Show'); + var btn = uiEl('button', 'kv-reveal', 'Show'); btn.type = 'button'; btn.onclick = function () { shown = !shown; val.textContent = shown ? f.raw : f.value; if (shown) { val.classList.remove('secret'); } else { val.classList.add('secret'); } - btn.textContent = shown ? 'Hide' : 'Show'; + btn.textContent = translateUiText(shown ? 'Hide' : 'Show'); }; row.appendChild(btn); } @@ -1798,9 +1874,9 @@ export const PAGE_HTML = ` }); body.appendChild(detailSection('Configuration', kv)); } else if (d.installed) { - body.appendChild(detailSection('Configuration', el('div', 'detail-path', 'bailian-cli is not wired into this agent yet.'))); + body.appendChild(detailSection('Configuration', uiEl('div', 'detail-path', 'bailian-cli is not wired into this agent yet.'))); } else { - body.appendChild(detailSection('Configuration', el('div', 'detail-path', 'This agent is not installed yet.'))); + body.appendChild(detailSection('Configuration', uiEl('div', 'detail-path', 'This agent is not installed yet.'))); } if (d.files && d.files.length) { var wrap = el('div', ''); @@ -1821,9 +1897,9 @@ export const PAGE_HTML = ` item.appendChild(el('pre', 'detail-code', fl.text)); sw.appendChild(item); }); - var openBtn = el('button', 'kv-reveal', 'Open'); + var openBtn = uiEl('button', 'kv-reveal', 'Open'); openBtn.type = 'button'; - openBtn.title = 'Open the config file with the system default app'; + openBtn.title = translateUiText('Open the config file with the system default app'); openBtn.onclick = function () { openAgentSettings(d); }; body.appendChild(detailSection('Settings', sw, openBtn)); } @@ -1848,18 +1924,23 @@ export const PAGE_HTML = ` renderScenarios(); }).catch(function (e) { renderError(body, e); }); } - var SCN_ORDER = ['图像', '音频', '视频', '多模态', '代码', '文档']; + function scenarioOrder() { + return UI_LANGUAGE === 'zh-CN' + ? ['图像', '音频', '视频', '多模态', '代码', '文档'] + : ['Image', 'Audio', 'Video', 'Multimodal', 'Code', 'Documentation']; + } function scenarioCategories() { var present = {}; + var order = scenarioOrder(); SCENARIOS.forEach(function (s) { if (s.category) present[s.category] = true; }); - var cats = SCN_ORDER.filter(function (c) { return present[c]; }); - Object.keys(present).forEach(function (c) { if (SCN_ORDER.indexOf(c) < 0) cats.push(c); }); + var cats = order.filter(function (c) { return present[c]; }); + Object.keys(present).forEach(function (c) { if (order.indexOf(c) < 0) cats.push(c); }); return cats; } function renderScenarioFilters() { var bar = document.getElementById('scenarioFilters'); bar.innerHTML = ''; - var defs = [{ key: 'all', label: 'All', n: SCENARIOS.length }]; + var defs = [{ key: 'all', label: translateUiText('All'), n: SCENARIOS.length }]; scenarioCategories().forEach(function (c) { defs.push({ key: c, label: c, n: SCENARIOS.filter(function (s) { return s.category === c; }).length }); }); @@ -1875,7 +1956,7 @@ export const PAGE_HTML = ` return matchQ(s.title, q) || matchQ(s.description, q) || matchQ(s.category, q); } function pgWarnNode() { - return el('div', 'pg-warn', 'No connected agent can accept tasks yet. Install and connect qwen-code (or another supported agent) first, and make sure its CLI is on your PATH.'); + return uiEl('div', 'pg-warn', 'No connected agent can accept tasks yet. Install and connect qwen-code (or another supported agent) first, and make sure its CLI is on your PATH.'); } function renderScenarios() { var body = document.getElementById('playgroundBody'); @@ -1896,7 +1977,7 @@ export const PAGE_HTML = ` var grid = el('div', 'grid'); info.items.forEach(function (s) { var t = el('div', 'tile clickable'); - t.title = 'Click to edit this scenario'; + t.title = translateUiText('Click to edit this scenario'); t.onclick = function () { openScnDrawer(s); }; var top = el('div', 'tile-top'); top.appendChild(el('span', 'tile-name', s.title)); @@ -1908,12 +1989,12 @@ export const PAGE_HTML = ` var actions = el('div', 'tile-actions'); var run = el('button', 'icon-run'); run.innerHTML = PLAY_SVG; - run.setAttribute('aria-label', 'Run it'); + run.setAttribute('aria-label', translateUiText('Run it')); if (DISPATCH_AGENTS.length) { - run.title = 'Dispatch this task to a local agent'; + run.title = translateUiText('Dispatch this task to a local agent'); run.onclick = function (e) { e.stopPropagation(); openDispatch(s); }; } else { - run.disabled = true; run.title = 'No connected agent available'; + run.disabled = true; run.title = translateUiText('No connected agent available'); } actions.appendChild(run); t.appendChild(actions); @@ -1943,7 +2024,7 @@ export const PAGE_HTML = ` if (!DISPATCH_SCN) return; document.getElementById('dispatchPreview').textContent = fillPrompt(DISPATCH_SCN, dispatchValues()); } - function setDispatchErr(m) { document.getElementById('dispatchErr').textContent = m || ''; } + function setDispatchErr(m) { document.getElementById('dispatchErr').textContent = translateUiText(m || ''); } var LAST_AGENT_KEY = 'bl.lastDispatchAgent'; function openDispatch(s) { DISPATCH_SCN = s; @@ -2052,7 +2133,7 @@ export const PAGE_HTML = ` return arr.filter(function (s) { return s && s.id && s.title && s.prompt; }).map(function (s) { return { id: s.id, title: s.title, description: s.description || '', - category: s.category || 'Custom', prompt: s.prompt, + category: s.category || translateUiText('Custom'), prompt: s.prompt, inputs: Array.isArray(s.inputs) ? s.inputs : [], custom: true }; }); @@ -2072,12 +2153,12 @@ export const PAGE_HTML = ` var l = el('input'); l.className = 'scn-l'; l.placeholder = 'label'; l.autocomplete = 'off'; l.spellcheck = false; var p = el('input'); p.className = 'scn-p'; p.placeholder = 'placeholder (optional)'; p.autocomplete = 'off'; p.spellcheck = false; if (inp) { k.value = inp.key || ''; l.value = inp.label || ''; p.value = inp.placeholder || ''; } - var del = el('button', 'btn-mini', '\u00d7'); del.type = 'button'; del.title = 'Remove'; del.onclick = function () { row.remove(); }; + var del = el('button', 'btn-mini', '\u00d7'); del.type = 'button'; del.title = translateUiText('Remove'); del.onclick = function () { row.remove(); }; row.appendChild(k); row.appendChild(l); row.appendChild(p); row.appendChild(del); return row; } function addScnInputRow(inp) { document.getElementById('scnInputRows').appendChild(scnInputRow(inp)); } - function setScnErr(m) { document.getElementById('scnDrawerErr').textContent = m || ''; } + function setScnErr(m) { document.getElementById('scnDrawerErr').textContent = translateUiText(m || ''); } function fillScnCatList() { var dl = document.getElementById('scnCatList'); dl.innerHTML = ''; scenarioCategories().forEach(function (c) { var o = document.createElement('option'); o.value = c; dl.appendChild(o); }); @@ -2086,9 +2167,9 @@ export const PAGE_HTML = ` SCN_EDIT_ID = existing ? existing.id : null; SCN_EDIT_KIND = existing ? (existing.custom ? 'custom' : 'builtin') : 'new'; var isEdit = !!existing; - document.getElementById('scnDrawerTitle').textContent = isEdit ? (existing.custom ? 'Edit scenario' : 'Edit preset scenario') : 'Custom scenario'; + document.getElementById('scnDrawerTitle').textContent = translateUiText(isEdit ? (existing.custom ? 'Edit scenario' : 'Edit preset scenario') : 'Custom scenario'); document.getElementById('scnTitle').value = existing ? existing.title : ''; - document.getElementById('scnCategory').value = existing ? (existing.category || '') : 'Custom'; + document.getElementById('scnCategory').value = existing ? (existing.category || '') : translateUiText('Custom'); document.getElementById('scnDesc').value = existing ? (existing.description || '') : ''; document.getElementById('scnPrompt').value = existing ? existing.prompt : ''; var rows = document.getElementById('scnInputRows'); rows.innerHTML = ''; @@ -2096,7 +2177,7 @@ export const PAGE_HTML = ` fillScnCatList(); var del = document.getElementById('scnDelete'); del.hidden = !isEdit; - del.textContent = 'Delete'; + del.textContent = translateUiText('Delete'); setScnErr(''); document.getElementById('scnDrawer').hidden = false; document.body.style.overflow = 'hidden'; @@ -2131,7 +2212,7 @@ export const PAGE_HTML = ` if (!prompt) { setScnErr('Prompt template is required.'); return; } var res = collectScnInputs(); if (res.error) { setScnErr(res.error); return; } - var cat = document.getElementById('scnCategory').value.trim() || 'Custom'; + var cat = document.getElementById('scnCategory').value.trim() || translateUiText('Custom'); var desc = document.getElementById('scnDesc').value.trim(); if (SCN_EDIT_KIND === 'builtin' && SCN_EDIT_ID && isBuiltin(SCN_EDIT_ID)) { var ov = loadOverrides(); @@ -2187,10 +2268,10 @@ export const PAGE_HTML = ` return '\uD83D\uDCC4'; } function assetKindLabel(kind) { - if (kind === 'image') return 'Image'; - if (kind === 'video') return 'Video'; - if (kind === 'audio') return 'Audio'; - return 'File'; + if (kind === 'image') return translateUiText('Image'); + if (kind === 'video') return translateUiText('Video'); + if (kind === 'audio') return translateUiText('Audio'); + return translateUiText('File'); } function fmtDim(w, h) { return w && h ? w + ' \u00d7 ' + h : ''; } @@ -2243,14 +2324,14 @@ export const PAGE_HTML = ` tabs.forEach(function (t) { var cat = t[0]; var b = el('button', 'filter' + (cat === ASSET_FILTER ? ' is-active' : '')); - b.appendChild(el('span', '', t[1])); + b.appendChild(uiEl('span', '', t[1])); b.appendChild(el('span', 'n', String(counts[cat] || 0))); b.onclick = function () { ASSET_FILTER = cat; ASSET_PAGE = 1; renderAssetFilters(); renderAssets(); }; bar.appendChild(b); }); var sort = el('button', 'filter sort-toggle'); - sort.title = 'Toggle sort by generation time'; - sort.textContent = ASSET_SORT === 'new' ? '↓ Newest first' : '↑ Oldest first'; + sort.title = translateUiText('Toggle sort by generation time'); + sort.textContent = translateUiText(ASSET_SORT === 'new' ? '↓ Newest first' : '↑ Oldest first'); sort.onclick = function () { ASSET_SORT = ASSET_SORT === 'new' ? 'old' : 'new'; ASSET_PAGE = 1; renderAssetFilters(); renderAssets(); }; bar.appendChild(sort); } @@ -2285,7 +2366,7 @@ export const PAGE_HTML = ` var mediaEl = null; if (a.kind === 'image') { var img = el('img'); img.src = src; img.loading = 'lazy'; img.alt = a.name; - img.title = 'View details'; + img.title = translateUiText('View details'); media.appendChild(img); mediaEl = img; } else if (a.kind === 'video') { var vid = el('video'); vid.src = src; vid.controls = true; vid.preload = 'metadata'; @@ -2297,16 +2378,16 @@ export const PAGE_HTML = ` media.appendChild(au); } else { var icon = el('span', 'asset-icon', assetIcon(a.kind)); - icon.title = 'View details'; + icon.title = translateUiText('View details'); media.appendChild(icon); } card.appendChild(media); card.appendChild(el('span', 'asset-cat', assetKindLabel(a.kind))); - var del = el('button', 'asset-del', '×'); del.title = 'Delete'; + var del = el('button', 'asset-del', '×'); del.title = translateUiText('Delete'); del.onclick = function (e) { e.stopPropagation(); deleteAsset(a); }; card.appendChild(del); var b = el('div', 'asset-body'); - var nm = el('div', 'asset-name link', a.name); nm.title = 'View details — ' + a.relPath; + var nm = el('div', 'asset-name link', a.name); nm.title = translateUiText('View details — ') + a.relPath; b.appendChild(nm); var folder = assetFolder(a.relPath); if (folder) { @@ -2363,9 +2444,9 @@ export const PAGE_HTML = ` body.appendChild(detailSection('Details', chips)); body.appendChild(detailSection('Path', el('div', 'detail-path', a.relPath))); openInfoDrawer(); - var openBtn = el('button', 'btn-primary', 'Open locally'); + var openBtn = uiEl('button', 'btn-primary', 'Open locally'); openBtn.onclick = function () { openAsset(a); }; - var delBtn = el('button', 'btn-danger', 'Delete'); + var delBtn = uiEl('button', 'btn-danger', 'Delete'); delBtn.onclick = function () { deleteAsset(a, true); }; var foot = document.getElementById('infoFoot'); foot.appendChild(openBtn); foot.appendChild(delBtn); @@ -2393,6 +2474,8 @@ export const PAGE_HTML = ` } /* ---------- wiring ---------- */ + captureStaticUiCopy(); + applyStaticUiCopy(); var navItems = document.querySelectorAll('.nav-item'); for (var n = 0; n < navItems.length; n++) { navItems[n].onclick = function () { showView(this.getAttribute('data-view')); }; @@ -2468,8 +2551,8 @@ export const PAGE_HTML = ` var btn = document.getElementById('copyUrl'); btn.onclick = function () { var done = function () { - btn.textContent = 'Copied'; btn.classList.add('copied'); - setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1400); + btn.textContent = translateUiText('Copied'); btn.classList.add('copied'); + setTimeout(function () { btn.textContent = translateUiText('Copy'); btn.classList.remove('copied'); }, 1400); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(url).then(done).catch(function () { fallbackCopy(url); done(); }); @@ -2485,10 +2568,10 @@ export const PAGE_HTML = ` } function authMethodLabel(p) { - if (p === 'console') return 'Console gateway'; - if (p === 'apiKey') return 'API key'; + if (p === 'console') return translateUiText('Console gateway'); + if (p === 'apiKey') return translateUiText('API key'); if (p === 'openapi') return 'OpenAPI (AK/SK)'; - return 'Account'; + return translateUiText('Account'); } function acctHue(seed) { var h = 0, s = seed || 'bl'; @@ -2533,7 +2616,7 @@ export const PAGE_HTML = ` var meta = []; if (st.region) meta.push(st.region); if (st.site) meta.push(st.site); - document.getElementById('acctMeta').textContent = meta.join(' · ') || 'Authenticated'; + document.getElementById('acctMeta').textContent = meta.join(' · ') || translateUiText('Authenticated'); var tok = document.getElementById('acctToken'); tok.textContent = st.masked || ''; tok.hidden = !st.masked; @@ -2542,7 +2625,7 @@ export const PAGE_HTML = ` closeAcctMenu(); loginBtn.hidden = false; loginBtn.disabled = false; - loginBtn.querySelector('span').textContent = 'Log in'; + loginBtn.querySelector('span').textContent = translateUiText('Log in'); } } function toggleAcctMenu() { @@ -2560,8 +2643,9 @@ export const PAGE_HTML = ` } function setLoginUi(busy, label) { var b = document.getElementById('loginBtn'); - if (b) { b.disabled = busy; b.querySelector('span').textContent = label; } - if (QS_LOGIN_BTN && document.body.contains(QS_LOGIN_BTN)) { QS_LOGIN_BTN.disabled = busy; QS_LOGIN_BTN.textContent = label; } + var translatedLabel = translateUiText(label); + if (b) { b.disabled = busy; b.querySelector('span').textContent = translatedLabel; } + if (QS_LOGIN_BTN && document.body.contains(QS_LOGIN_BTN)) { QS_LOGIN_BTN.disabled = busy; QS_LOGIN_BTN.textContent = translatedLabel; } } function startLogin() { setLoginUi(true, 'Opening browser…'); @@ -2607,3 +2691,7 @@ export const PAGE_HTML = ` `; + +export function renderConfigUiHtml(language: Language): string { + return renderConfigUiShell(PAGE_HTML, language); +} diff --git a/packages/commands/src/commands/config/ui-i18n.ts b/packages/commands/src/commands/config/ui-i18n.ts new file mode 100644 index 00000000..d0907d88 --- /dev/null +++ b/packages/commands/src/commands/config/ui-i18n.ts @@ -0,0 +1,369 @@ +import type { Language } from "bailian-cli-core"; + +/** + * Config UI is a self-contained HTML document, so this catalog is embedded in + * the page and applied only to UI-owned text and attributes in the browser. + * Replacements are longest-first to keep a short label such as "Save" from + * changing a longer sentence before it is matched. + */ +const ZH_CN_REPLACEMENTS: ReadonlyArray = [ + ["Config - Alibaba Cloud Model Studio CLI", "配置 - 阿里云百炼 CLI"], + [ + 'The login was not completed in time. Click "Log in" to try again.', + "登录未能及时完成。请点击“登录”重试。", + ], + [ + "No connected agent can accept tasks yet. Install and connect qwen-code (or another supported agent) first, and make sure its CLI is on your PATH.", + "当前没有可接收任务的已连接 Agent。请先安装并连接 qwen-code(或其他受支持的 Agent),并确保其 CLI 位于 PATH 中。", + ], + [ + "Complete a few steps to check your local environment and finish first-time setup: sign in to Bailian, connect a local coding agent, and run your first task. Each circle turns from gray to green as you go.", + "完成以下步骤以检查本地环境并完成首次配置:登录百炼、连接本地编程 Agent,并运行第一个任务。完成后,每个圆点会由灰色变为绿色。", + ], + [ + 'Pick a scenario to dispatch a preset task to a connected local coding agent, running in a new terminal. Tasks run in the directory where bl config ui was started.', + '选择一个场景,将预设任务发送给已连接的本地编程 Agent,并在新终端中运行。任务会在启动 bl config ui 的目录中执行。', + ], + [ + "Pick a scenario to dispatch a preset task to a connected local coding agent, running in a new terminal. Tasks run in the directory where ", + "选择一个场景,将预设任务发送给已连接的本地编程 Agent,并在新终端中运行。任务会在启动 ", + ], + [" was started.", " 的目录中执行。"], + [ + "Credentials and default models. The active profile (marked with a star) is used by every bl command. Click a profile to edit its settings.", + "管理凭证和默认模型。所有 bl 命令都会使用带星标的已激活 Profile。点击 Profile 可编辑其设置。", + ], + [ + 'Agent skills discovered across every local agent module (~/.agents/skills plus each agent\'s skills folder). Installed via npx skills add.', + '展示从本地各 Agent 模块中发现的 Skill(~/.agents/skills 以及各 Agent 的 skills 目录),可通过 npx skills add 安装。', + ], + [ + "Agent skills discovered across every local agent module (~/.agents/skills plus each agent's skills folder). Installed via ", + "展示从本地各 Agent 模块中发现的 Skill(~/.agents/skills 以及各 Agent 的 skills 目录),可通过 ", + ], + [ + "Media that bl writes into the output directory, grouped by type (images, videos, audio, files) and generation time.", + "bl 写入输出目录的媒体文件,按类型(图片、视频、音频、文件)和生成时间展示。", + ], + [ + 'Frameworks bl can configure. "Connected" means the bailian-cli provider is wired into that agent.', + 'bl 可以配置的编程 Agent。"已连接"表示该 Agent 已接入 bailian-cli provider。', + ], + [ + "Model Context Protocol servers declared in your local coding-agent configs.", + "本地编程 Agent 配置中声明的 Model Context Protocol 服务器。", + ], + [ + "Create a named profile with its own credentials and default models.", + "创建一个拥有独立凭证和默认模型的命名 Profile。", + ], + [ + "Inputs become fillable fields when dispatching. Reference each one in the prompt as {{key}}.", + "任务发送时,输入项会显示为可填写字段。请在提示词中使用 {{key}} 引用对应输入。", + ], + [ + "The .zip must contain a SKILL.md at its root or inside a single top-level folder.", + ".zip 根目录或唯一的顶层目录中必须包含 SKILL.md。", + ], + ["The active profile is used by every bl command.", "所有 bl 命令都会使用已激活的 Profile。"], + ['Delete profile "', "删除 Profile“"], + [ + '"? This permanently removes its credentials and default models.', + "”?这会永久删除其中的凭证和默认模型。", + ], + ['Delete asset "', "删除资产“"], + ['"? This removes the file from disk.', "”?这会从磁盘中删除该文件。"], + [ + "MCP servers configured in Claude Code, Codex, Qwen Code or OpenCode will appear here.", + "在 Claude Code、Codex、Qwen Code 或 OpenCode 中配置的 MCP 服务器会显示在这里。", + ], + [ + "Assets from bl image, bl video, bl speech and bl omni will appear here.", + "通过 bl imagebl videobl speechbl omni 生成的资产会显示在这里。", + ], + ["Remote agents loaded from a URL will appear here.", "通过 URL 加载的远程 Agent 会显示在这里。"], + [ + "The CLI for this agent was not found on your PATH — install it before launching", + "未在 PATH 中找到该 Agent 的 CLI,请先安装再启动", + ], + [ + "Connect this agent to bailian-cli before launching", + "请先将该 Agent 连接到 bailian-cli 再启动", + ], + ["Install this agent before launching", "请先安装该 Agent 再启动"], + ["Open a new terminal and start this agent", "在新终端中启动该 Agent"], + ["Open the config file with the system default app", "使用系统默认应用打开配置文件"], + ["Task sent to ", "任务已发送至 "], + [". Check the newly opened terminal window.", "。请查看新打开的终端窗口。"], + ["Please enter a profile name.", "请输入 Profile 名称。"], + ["Only letters, numbers, - and _ are allowed.", "仅允许使用字母、数字、- 和 _。"], + ['"default" is reserved for the top-level profile.', "“default”保留用于顶层 Profile。"], + ['A profile named "', "名为“"], + ['" already exists.', "”的 Profile 已存在。"], + ["Saved, but activation failed: ", "已保存,但激活失败:"], + ["Could not read runtime environment info.", "无法读取运行环境信息。"], + ["Runtime Node ", "运行时 Node "], + [" · platform ", " · 平台 "], + ["Signed in: ", "已登录:"], + ["Connected: ", "已连接:"], + ["All agents (~/.agents/skills)", "所有 Agent(~/.agents/skills)"], + [ + "Optional — folder name (defaults to the archive folder)", + "可选 — 文件夹名称(默认使用压缩包中的文件夹名称)", + ], + ["Skill name (optional)", "Skill 名称(可选)"], + ["Skill package (.zip)", "Skill 包(.zip)"], + ["Please choose a .zip file.", "请选择 .zip 文件。"], + [ + "Install with npx skills add modelstudioai/cli --all -g", + "使用 npx skills add modelstudioai/cli --all -g 安装", + ], + ['Skill "', "Skill“"], + ['MCP server "', "MCP 服务器“"], + ['" installed (', "”已安装("], + [" files) to ", " 个文件),位置:"], + ['" was updated.', "”已更新。"], + ['" was added to ', "”已添加至 "], + ['Remove "', "移除“"], + ['" from ', "”(来源:"], + ["? This rewrites the source config file.", ")?此操作会重写来源配置文件。"], + ["Could not delete this server.", "无法删除该服务器。"], + ["Delete MCP server?", "删除 MCP 服务器?"], + ["Config must be a JSON object.", "配置必须是 JSON 对象。"], + ["Invalid JSON: ", "无效的 JSON:"], + ["Please enter a server name.", "请输入服务器名称。"], + ["No remote agents yet.", "暂无远程 Agent。"], + ["No agents in this category.", "该分类下暂无 Agent。"], + ['No agents match "', "没有匹配“"], + ["bailian-cli is not wired into this agent yet.", "该 Agent 尚未连接 bailian-cli。"], + ["This agent is not installed yet.", "该 Agent 尚未安装。"], + ["No scenarios in this category.", "该分类下暂无场景。"], + ['No scenarios match "', "没有匹配“"], + ["Click to edit this scenario", "点击编辑此场景"], + ["Dispatch this task to a local agent", "将此任务发送至本地 Agent"], + ["No connected agent available", "没有可用的已连接 Agent"], + ["Please fill in: ", "请填写:"], + ['Input key "', "输入键“"], + ['" may only use letters, digits, underscore.', "”只能使用字母、数字和下划线。"], + ["Duplicate input key: ", "输入键重复:"], + ["Title is required.", "标题为必填项。"], + ["Prompt template is required.", "提示词模板为必填项。"], + ['No assets match "', "没有匹配“"], + ["Toggle sort by generation time", "切换生成时间排序"], + ["↓ Newest first", "↓ 最新优先"], + ["↑ Oldest first", "↑ 最早优先"], + ["View details — ", "查看详情 — "], + ["Available ", "可用的 "], + [" models · click to use", " 模型 · 点击使用"], + [ + "Applies to bl video generate only (text/image-to-video). ", + "仅适用于 bl video generate(文生视频/图生视频)。", + ], + [ + "bl video ref (multi-image) and bl video edit keep their own ", + "bl video ref(多图)和 bl video edit 仍使用各自的", + ], + [ + "fixed models — pass --model to override those per run.", + "固定模型;可在每次运行时通过 --model 覆盖。", + ], + ['No skills match "', "没有匹配“"], + ['No MCP servers match "', "没有匹配“"], + ["Install to", "安装到"], + ["Transport / Source", "传输方式 / 来源"], + ["Target agent config", "目标 Agent 配置"], + ["Server name", "服务器名称"], + ["Quick launch", "快速启动"], + ["Config files", "配置文件"], + ["Environment check", "环境检查"], + ["Edit preset scenario", "编辑预设场景"], + ["Edit scenario", "编辑场景"], + ["Save & Activate", "保存并激活"], + ["Are you sure?", "确认执行此操作吗?"], + ["Save failed", "保存失败"], + ["Create failed", "创建失败"], + ["Install failed", "安装失败"], + ["Launch failed", "启动失败"], + ["Dispatch failed", "发送失败"], + ["Delete failed", "删除失败"], + ["Created", "已创建"], + ["Dispatched", "已发送"], + ["Saved", "已保存"], + ["OK", "确定"], + ["Configuration", "配置"], + ["Settings", "设置"], + ["Status", "状态"], + ["Open", "打开"], + ["Remove", "移除"], + ["Show", "显示"], + ["Hide", "隐藏"], + ["All", "全部"], + ["Images", "图片"], + ["Videos", "视频"], + ["Files", "文件"], + ["Image", "图片"], + ["Video", "视频"], + ["Audio", "音频"], + ["File", "文件"], + [" files", " 个文件"], + ["MCPs", "MCP 服务"], + ["Coding", "编程"], + ["Generated", "生成的"], + ["API key", "API 密钥"], + ["Console gateway", "控制台网关"], + ["Account", "账户"], + ["(empty)", "(空)"], + [" (editable)", "(可编辑)"], + [" (missing)", "(缺失)"], + ["default (top-level)", "default(顶层)"], + [ + "This source is read-only here (its config is TOML). Edit it directly in ", + "该来源在此处为只读(配置格式为 TOML)。请直接编辑:", + ], + [ + "No connected local coding agent detected (e.g. qwen-code). Open the Agents page to install and connect one.", + "未检测到已连接的本地编程 Agent(例如 qwen-code)。请打开 Agent 页面进行安装和连接。", + ], + ["Installed but not wired into bl: ", "已安装但尚未接入 bl:"], + [". Open the Agents page to finish connecting.", "。请打开 Agent 页面完成连接。"], + [ + "Finish the previous step first (connect a dispatchable agent), then come back to Playground to run your first scenario.", + "请先完成上一步(连接可接收任务的 Agent),再回到 Playground 运行第一个场景。", + ], + [ + "Go to Playground, pick a scenario and click Run it to complete your first dispatch.", + "前往 Playground,选择一个场景并点击“运行”,完成首次任务发送。", + ], + [ + "Sign in to the Bailian console to obtain credentials (opens a login page in your browser).", + "登录百炼控制台以获取凭证(将在浏览器中打开登录页面)。", + ], + ["You have dispatched at least one task.", "你已经成功发送过至少一个任务。"], + ["Node 18 or newer is recommended.", "建议使用 Node.js 18 或更高版本。"], + ["Save & Activate", "保存并激活"], + ["Save scenario", "保存场景"], + ["Custom scenario", "自定义场景"], + ["+ Custom scenario", "+ 自定义场景"], + ["New profile", "新建 Profile"], + ["+ New profile", "+ 新建 Profile"], + ["Profile name", "Profile 名称"], + ["Target agent", "目标 Agent"], + ["Task to dispatch", "要发送的任务"], + ["Prompt template", "提示词模板"], + ["One-line description (optional)", "一句话描述(可选)"], + ["Use {{key}} placeholders for inputs", "使用 {{key}} 作为输入占位符"], + ["e.g. Translate docs to English", "例如:将文档翻译成英文"], + ["e.g. Image / Custom", "例如:图像 / 自定义"], + ["e.g. work, intl, test", "例如:work、intl、test"], + ["Search scenarios…", "搜索场景…"], + ["Search skills…", "搜索 Skill…"], + ["Search MCP servers…", "搜索 MCP 服务器…"], + ["Search agents…", "搜索 Agent…"], + ["Search assets…", "搜索资产…"], + ["Installed Skills", "已安装的 Skill"], + ["Coding Agents", "编程 Agent"], + ["Generated Assets", "生成资产"], + ["Get Started", "开始使用"], + ["Quick Start", "快速开始"], + ["Playground", "Playground"], + ["Extensions", "扩展"], + ["Workspace", "工作区"], + ["Skills", "Skill"], + ["Agents", "Agent"], + ["Assets", "资产"], + ["Config", "配置"], + ["Log in", "登录"], + ["Log out", "退出登录"], + ["Copy this URL", "复制此链接"], + ["QR code for this session URL", "当前会话链接的二维码"], + ["Collapse sidebar", "收起侧边栏"], + ["Expand sidebar", "展开侧边栏"], + ["Model Studio CLI home", "百炼 CLI 首页"], + ["Loading…", "加载中…"], + ["Delete profile", "删除 Profile"], + ["Delete asset", "删除资产"], + ["Open failed", "打开失败"], + ["Open locally", "在本地打开"], + ["View details", "查看详情"], + ["Jump backward 5 pages", "向前跳转 5 页"], + ["Jump forward 5 pages", "向后跳转 5 页"], + ["Items per page", "每页数量"], + ["No generated assets yet.", "暂无生成资产。"], + ["No assets in this category.", "该分类下暂无资产。"], + ["No skills installed.", "尚未安装 Skill。"], + ["No local MCP servers found.", "未发现本地 MCP 服务器。"], + ["No coding agents found.", "未发现编程 Agent。"], + ["No scenarios found.", "未发现场景。"], + ["Failed to load: ", "加载失败:"], + ["Load failed: ", "加载失败:"], + ["Save failed: ", "保存失败:"], + ["Create failed: ", "创建失败:"], + ["Install failed: ", "安装失败:"], + ["Launch failed: ", "启动失败:"], + ["Saving…", "正在保存…"], + ["Saved and activated.", "已保存并激活。"], + ["Saved.", "已保存。"], + ["Profile created and saved.", "Profile 已创建并保存。"], + ["Opening browser…", "正在打开浏览器…"], + ["Waiting for login…", "等待登录…"], + ["Signed in…", "已登录…"], + ["Login timed out", "登录超时"], + ["Authenticated", "已认证"], + ["Connect a local agent", "连接本地 Agent"], + ["Run your first task", "运行第一个任务"], + ["Sign in to Bailian", "登录百炼"], + ["Check local environment", "检查本地环境"], + ["Go to Playground", "前往 Playground"], + ["Go to Agents", "前往 Agent"], + ["Run it", "运行"], + ["Done", "已完成"], + ["Connected", "已连接"], + ["Not installed", "未安装"], + ["Installed", "已安装"], + ["Launching…", "正在启动…"], + ["Launched → ", "已启动 → "], + ["Remote", "远程"], + ["Local", "本地"], + ["Add skill", "添加 Skill"], + ["+ Add skill", "+ 添加 Skill"], + ["Choose .zip file", "选择 .zip 文件"], + ["No file selected", "未选择文件"], + ["New MCP server", "新建 MCP 服务器"], + ["+ Add MCP", "+ 添加 MCP"], + ["Description", "描述"], + ["Installed in", "安装位置"], + ["Details", "详情"], + ["Type", "类型"], + ["Path", "路径"], + ["Scope", "范围"], + ["Title", "标题"], + ["Category", "分类"], + ["Inputs", "输入项"], + ["Add input", "添加输入"], + ["Create", "创建"], + ["Install", "安装"], + ["Delete", "删除"], + ["Cancel", "取消"], + ["Close", "关闭"], + ["Active", "已激活"], + ["Save", "保存"], + ["Copy", "复制"], + ["Copied", "已复制"], + ["show", "显示"], + ["hide", "隐藏"], + ["(unset)", "(未设置)"], + [" / page", " / 页"], +]; + +function serializeTranslations(): string { + return JSON.stringify( + [...ZH_CN_REPLACEMENTS].sort(([englishA], [englishB]) => englishB.length - englishA.length), + ).replaceAll("<", "\\u003c"); +} + +export function renderConfigUiShell(html: string, language: Language): string { + return html + .replace('', ``) + .replace("__BL_CONFIG_UI_LANGUAGE__", language) + .replace("__BL_CONFIG_UI_TRANSLATIONS__", serializeTranslations()); +} diff --git a/packages/commands/src/commands/config/ui.ts b/packages/commands/src/commands/config/ui.ts index 5a12de0d..8e86d924 100644 --- a/packages/commands/src/commands/config/ui.ts +++ b/packages/commands/src/commands/config/ui.ts @@ -12,13 +12,15 @@ import { readConfigFile, writeConfigFile, deleteConfigProfile, + DEFAULT_LANGUAGE, REGIONS, type ConfigStore, type FlagsDef, + type Language, } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; import { listenLocalServer, openInBrowser, openPath } from "../shared/local-server.ts"; -import { PAGE_HTML } from "./ui-html.ts"; +import { renderConfigUiHtml } from "./ui-html.ts"; import { UI_VALID_KEYS, UI_ENUM_KEYS, @@ -40,7 +42,12 @@ import { installSkillZip, } from "./inventory.ts"; import { launchAgent, agentLaunchable, agentSupportsPrompt } from "./agent-launch.ts"; -import { SCENARIOS, getScenario, renderScenarioPrompt, type Scenario } from "./scenarios.ts"; +import { + getScenario, + localizeScenarios, + renderScenarioPrompt, + type Scenario, +} from "./scenarios.ts"; import { qrSvg } from "./qr.ts"; import { makeAuthUiBridge, type AuthUiBridge } from "../auth/console-ui.ts"; import { listAssets, resolveAssetPath, defaultOutputBase, contentType } from "./assets.ts"; @@ -49,9 +56,18 @@ const FLAGS = { port: { type: "number", valueHint: "", - description: "Port to listen on (default: random free port)", + description: { + "en-US": "Port to listen on (default: random free port)", + "zh-CN": "监听端口(默认:随机可用端口)", + }, + }, + noOpen: { + type: "switch", + description: { + "en-US": "Do not open the browser automatically", + "zh-CN": "不自动打开浏览器", + }, }, - noOpen: { type: "switch", description: "Do not open the browser automatically" }, } satisfies FlagsDef; const MAX_BODY = 1 << 20; // 1 MiB @@ -69,6 +85,10 @@ function sendJson(res: http.ServerResponse, status: number, obj: unknown): void res.end(JSON.stringify(obj)); } +function configUiLanguage(configStore: ConfigStore): Language { + return configStore.read().language ?? DEFAULT_LANGUAGE; +} + function readBody(req: http.IncomingMessage): Promise { return new Promise((resolve, reject) => { let size = 0; @@ -156,6 +176,13 @@ export function createConfigUiServer( outputBase: string = defaultOutputBase(), authBridge?: AuthUiBridge, ): http.Server { + let activatedUiProfile: string | null = null; + const uiLanguage = (): Language => { + if (activatedUiProfile === null) return configUiLanguage(configStore); + const configName = activatedUiProfile === "default" ? undefined : activatedUiProfile; + return readConfigFile(configName).language ?? DEFAULT_LANGUAGE; + }; + return http.createServer(async (req, res) => { try { const host = (req.headers.host || "").split(":")[0]; @@ -186,7 +213,7 @@ export function createConfigUiServer( "img-src 'self' data: https://img.alicdn.com https://oss.aliyuncs.com; " + "media-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'", }); - res.end(PAGE_HTML); + res.end(renderConfigUiHtml(uiLanguage())); return; } @@ -371,7 +398,10 @@ export function createConfigUiServer( dispatchable: launchable[i] && agentSupportsPrompt(a.id), })) .filter((a) => a.dispatchable); - sendJson(res, 200, { scenarios: SCENARIOS, agents: targets }); + sendJson(res, 200, { + scenarios: localizeScenarios(uiLanguage()), + agents: targets, + }); return; } @@ -534,7 +564,10 @@ export function createConfigUiServer( inputs, }; } else { - scenario = typeof body.scenario === "string" ? getScenario(body.scenario) : undefined; + scenario = + typeof body.scenario === "string" + ? getScenario(body.scenario, uiLanguage()) + : undefined; } if (!scenario) { sendJson(res, 400, { error: "unknown scenario" }); @@ -579,7 +612,8 @@ export function createConfigUiServer( const body = parsed as { name?: unknown }; try { const activeProfile = await configStore.activate(body.name); - sendJson(res, 200, { activeProfile }); + activatedUiProfile = activeProfile; + sendJson(res, 200, { activeProfile, uiLanguage: uiLanguage() }); } catch (err) { sendJson(res, 400, { error: errMessage(err) }); } @@ -612,7 +646,7 @@ export function createConfigUiServer( const existing = readConfigFile(normalized) as Record; const saved = mergeUnmanagedProfileFields(existing, cleaned); await writeConfigFile(saved, normalized); - sendJson(res, 200, { saved }); + sendJson(res, 200, { saved, uiLanguage: uiLanguage() }); return; } @@ -642,7 +676,10 @@ export function createConfigUiServer( } export default defineCommand({ - description: "Open a local web UI to manage config profiles", + description: { + "en-US": "Open a local web UI to manage config profiles", + "zh-CN": "打开用于管理配置 Profile 的本地 Web UI", + }, auth: "none", usageArgs: "[--port ] [--no-open]", flags: FLAGS, diff --git a/packages/commands/src/commands/config/use.ts b/packages/commands/src/commands/config/use.ts index 13de63a7..e85137c9 100644 --- a/packages/commands/src/commands/config/use.ts +++ b/packages/commands/src/commands/config/use.ts @@ -2,14 +2,17 @@ import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; export default defineCommand({ - description: "Set the active config profile", + description: { "en-US": "Set the active config profile", "zh-CN": "设置当前激活的配置 Profile" }, auth: "none", usageArgs: "--name ", flags: { name: { type: "string", valueHint: "", - description: "Existing profile name, or default", + description: { + "en-US": "Existing profile name, or default", + "zh-CN": "已有 Profile 名称,或 default", + }, required: true, }, }, diff --git a/packages/commands/src/commands/console/call.ts b/packages/commands/src/commands/console/call.ts index 0a6a8791..2ff9bfa4 100644 --- a/packages/commands/src/commands/console/call.ts +++ b/packages/commands/src/commands/console/call.ts @@ -7,20 +7,26 @@ import { import { emitResult } from "bailian-cli-runtime"; export default defineCommand({ - description: "Call a Bailian console API via the CLI gateway", + description: { + "en-US": "Call a Bailian console API via the CLI gateway", + "zh-CN": "通过 CLI Gateway 调用百炼控制台 API", + }, auth: "console", usageArgs: "--api --data [flags]", flags: { api: { type: "string", valueHint: "", - description: "API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries)", + description: { + "en-US": "API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries)", + "zh-CN": "API 名称(例如 zeldaEasy.broadscope-bailian.memory-library.getLibraries)", + }, required: true, }, data: { type: "string", valueHint: "", - description: "Request data as JSON string", + description: { "en-US": "Request data as JSON string", "zh-CN": "JSON 字符串格式的请求数据" }, required: true, }, }, diff --git a/packages/commands/src/commands/dataset/delete.ts b/packages/commands/src/commands/dataset/delete.ts index 4c1e888b..520cfaf4 100644 --- a/packages/commands/src/commands/dataset/delete.ts +++ b/packages/commands/src/commands/dataset/delete.ts @@ -5,13 +5,13 @@ const DELETE_FLAGS = { fileId: { type: "string", valueHint: "", - description: "Dataset file ID (required)", + description: { "en-US": "Dataset file ID (required)", "zh-CN": "数据集文件 ID(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Delete a dataset file by ID", + description: { "en-US": "Delete a dataset file by ID", "zh-CN": "通过 ID 删除数据集文件" }, auth: "apiKey", usageArgs: "--file-id ", flags: DELETE_FLAGS, diff --git a/packages/commands/src/commands/dataset/get.ts b/packages/commands/src/commands/dataset/get.ts index f9a41451..f9bb3674 100644 --- a/packages/commands/src/commands/dataset/get.ts +++ b/packages/commands/src/commands/dataset/get.ts @@ -5,13 +5,16 @@ const GET_FLAGS = { fileId: { type: "string", valueHint: "", - description: "Dataset file ID (required)", + description: { "en-US": "Dataset file ID (required)", "zh-CN": "数据集文件 ID(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Get details of a single dataset file", + description: { + "en-US": "Get details of a single dataset file", + "zh-CN": "获取单个数据集文件的详情", + }, auth: "apiKey", usageArgs: "--file-id ", flags: GET_FLAGS, diff --git a/packages/commands/src/commands/dataset/list.ts b/packages/commands/src/commands/dataset/list.ts index 63bbfeb2..7d37d6fa 100644 --- a/packages/commands/src/commands/dataset/list.ts +++ b/packages/commands/src/commands/dataset/list.ts @@ -2,21 +2,31 @@ import { defineCommand, listDatasets, type FlagsDef } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; const LIST_FLAGS = { - page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + page: { + type: "number", + valueHint: "", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, + }, pageSize: { type: "number", valueHint: "", - description: "Results per page (default: 10, max 100)", + description: { + "en-US": "Results per page (default: 10, max 100)", + "zh-CN": "每页结果数(默认:10,最多:100)", + }, }, purpose: { type: "string", valueHint: "", - description: 'Filter by purpose (e.g. "fine-tune", "evaluation"). Omit to list all.', + description: { + "en-US": 'Filter by purpose (e.g. "fine-tune", "evaluation"). Omit to list all.', + "zh-CN": '按用途筛选(例如 "fine-tune"、"evaluation")。省略时列出全部。', + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "List uploaded dataset files", + description: { "en-US": "List uploaded dataset files", "zh-CN": "列出已上传的数据集文件" }, auth: "apiKey", usageArgs: "[--page ] [--page-size ] [--purpose ]", flags: LIST_FLAGS, diff --git a/packages/commands/src/commands/dataset/upload.ts b/packages/commands/src/commands/dataset/upload.ts index 822c5a9f..48677ba2 100644 --- a/packages/commands/src/commands/dataset/upload.ts +++ b/packages/commands/src/commands/dataset/upload.ts @@ -17,32 +17,52 @@ const UPLOAD_FLAGS = { file: { type: "string", valueHint: "", - description: "Local dataset file (.jsonl or .zip; ≤200MB SFT/DPO, ≤300MB CPT, ≤2GB media zip)", + description: { + "en-US": "Local dataset file (.jsonl or .zip; ≤200MB SFT/DPO, ≤300MB CPT, ≤2GB media zip)", + "zh-CN": + "本地数据集文件(.jsonl 或 .zip;SFT/DPO 不超过 200MB,CPT 不超过 300MB,媒体 ZIP 不超过 2GB)", + }, required: true, }, purpose: { type: "string", valueHint: "", - description: 'Dataset purpose tag (default: "fine-tune"; e.g. "evaluation")', + description: { + "en-US": 'Dataset purpose tag (default: "fine-tune"; e.g. "evaluation")', + "zh-CN": '数据集用途标签(默认:"fine-tune";例如 "evaluation")', + }, }, schema: { type: "string", valueHint: "", - description: - 'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.', + description: { + "en-US": + 'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.', + "zh-CN": + '记录 Schema:"chatml"(SFT)、"dpo"(chosen/rejected)、"cpt"(原始文本)、"tts"(音频)、"image"(图片生成)或 "video"(视频生成)。默认逐条自动识别。', + }, }, noValidate: { type: "switch", - description: "Skip the local JSONL pre-flight check (not recommended)", + description: { + "en-US": "Skip the local JSONL pre-flight check (not recommended)", + "zh-CN": "跳过本地 JSONL 预检查(不推荐)", + }, }, fullValidate: { type: "switch", - description: "JSON.parse every line instead of sampling (slower)", + description: { + "en-US": "JSON.parse every line instead of sampling (slower)", + "zh-CN": "使用 JSON.parse 检查每一行,而不是抽样检查(速度较慢)", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "Upload a dataset file (.jsonl or .zip) to Bailian", + description: { + "en-US": "Upload a dataset file (.jsonl or .zip) to Bailian", + "zh-CN": "将数据集文件(.jsonl 或 .zip)上传到百炼", + }, auth: "apiKey", usageArgs: "--file [--purpose ] [--schema ] [--no-validate] [--full-validate]", @@ -57,17 +77,24 @@ export default defineCommand({ "--file train.jsonl --no-validate", ], notes: [ - "Supports .jsonl (text) and .zip (audio/image archives with a data.jsonl", - "manifest). Six record schemas are recognized: chatml = {messages:[...]}", - '(SFT); dpo = {messages:[...], chosen, rejected}; cpt = {text:"..."}', - '(continual pre-training, raw text); tts = {wav_fn:"train/xxx.wav",', - 'text:"..."} (audio fine-tuning); image = {img_path:"..."} (image', - "generation); video = {first_frame_path:...} (video generation). With no", - "--schema, a record carrying wav_fn is validated as TTS, img_path as image,", - "chosen/rejected as DPO, text (no messages) as CPT, otherwise ChatML.", - "Upload cap: 200MB SFT/DPO text, 300MB CPT, 2GB media zip. Upload uses the", - "OpenAI-compatible /compatible-mode/v1/files endpoint so the purpose tag is", - "persisted (the DashScope-native /api/v1/files drops it).", + { + "en-US": + 'Supports .jsonl (text) and .zip (audio/image/video archives with a data.jsonl manifest). Six record schemas are recognized: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen, rejected}; cpt = {text:"..."} (continual pre-training, raw text); tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning); image = {img_path:"..."} (image generation); video = {first_frame_path:...} (video generation).', + "zh-CN": + '支持 .jsonl(文本)和 .zip(包含 data.jsonl 清单的音频、图片或视频归档)。可识别六种记录 Schema:chatml = {messages:[...]}(SFT);dpo = {messages:[...], chosen, rejected};cpt = {text:"..."}(持续预训练,原始文本);tts = {wav_fn:"train/xxx.wav", text:"..."}(音频微调);image = {img_path:"..."}(图片生成);video = {first_frame_path:...}(视频生成)。', + }, + { + "en-US": + "With no --schema, a record carrying wav_fn is validated as TTS, img_path as image, chosen/rejected as DPO, text (no messages) as CPT, otherwise ChatML.", + "zh-CN": + "未指定 --schema 时,包含 wav_fn 的记录按 TTS 验证,包含 img_path 的按 image 验证,包含 chosen/rejected 的按 DPO 验证,仅含 text(无 messages)的按 CPT 验证,其他记录按 ChatML 验证。", + }, + { + "en-US": + "Upload cap: 200MB SFT/DPO text, 300MB CPT, 2GB media zip. Upload uses the OpenAI-compatible /compatible-mode/v1/files endpoint so the purpose tag is persisted (the DashScope-native /api/v1/files drops it).", + "zh-CN": + "上传上限:SFT/DPO 文本 200MB、CPT 300MB、媒体 ZIP 2GB。上传使用 OpenAI 兼容的 /compatible-mode/v1/files Endpoint,以便保留 purpose 标签;DashScope 原生 /api/v1/files 会丢弃该标签。", + }, ], async run(ctx) { const { identity, settings, flags } = ctx; diff --git a/packages/commands/src/commands/dataset/validate.ts b/packages/commands/src/commands/dataset/validate.ts index b9e7f62b..055a2103 100644 --- a/packages/commands/src/commands/dataset/validate.ts +++ b/packages/commands/src/commands/dataset/validate.ts @@ -12,23 +12,36 @@ const VALIDATE_FLAGS = { file: { type: "string", valueHint: "", - description: "Local dataset file (.jsonl or .zip)", + description: { + "en-US": "Local dataset file (.jsonl or .zip)", + "zh-CN": "本地数据集文件(.jsonl 或 .zip)", + }, required: true, }, fullValidate: { type: "switch", - description: "JSON.parse every line instead of sampling (slower)", + description: { + "en-US": "JSON.parse every line instead of sampling (slower)", + "zh-CN": "使用 JSON.parse 检查每一行,而不是抽样检查(速度较慢)", + }, }, schema: { type: "string", valueHint: "", - description: - 'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.', + description: { + "en-US": + 'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.', + "zh-CN": + '记录 Schema:"chatml"(SFT)、"dpo"(chosen/rejected)、"cpt"(原始文本)、"tts"(音频)、"image"(图片生成)或 "video"(视频生成)。默认逐条自动识别。', + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "Locally validate a dataset file (.jsonl or .zip) without uploading", + description: { + "en-US": "Locally validate a dataset file (.jsonl or .zip) without uploading", + "zh-CN": "在本地验证数据集文件(.jsonl 或 .zip),不执行上传", + }, // 纯本地校验,不触网、不需 API key(与 `pipeline validate` 一致)。 auth: "none", usageArgs: "--file [--full-validate] [--schema ]", @@ -43,20 +56,30 @@ export default defineCommand({ "--file train.jsonl --output json", ], notes: [ - "Default scan: every line gets a structural check, then ~160 lines (front 50,", - "evenly spaced 100, last 10) are JSON.parsed against the active schema.", - "Schemas: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen,", - 'rejected}; cpt = {text:"..."} (continual pre-training, raw text);', - 'tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning);', - 'image = {img_path:"..."} (image generation);', - 'video = {first_frame_path:"...", video_path:"..."} (video generation,', - "i2v first-frame or kf2v first+last-frame with last_frame_path). With no", - "--schema, a record carrying wav_fn is validated as TTS, img_path as image,", - "first_frame_path/video_path as video, chosen/rejected as DPO, text (no", - "messages) as CPT, otherwise ChatML. Pass --schema to require a specific", - "shape on every record. ZIP archives (.zip) are validated structurally", - "(data.jsonl present, media references resolve) in addition to per-record", - "content checks. Use --full-validate to JSON.parse every line.", + { + "en-US": + "Default scan: every line gets a structural check, then ~160 lines (front 50, evenly spaced 100, last 10) are JSON.parsed against the active schema.", + "zh-CN": + "默认扫描:先对每一行进行结构检查,再抽取约 160 行(前 50 行、均匀抽取 100 行、最后 10 行),使用 JSON.parse 按当前 Schema 验证。", + }, + { + "en-US": + 'Schemas: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen, rejected}; cpt = {text:"..."} (continual pre-training, raw text); tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning); image = {img_path:"..."} (image generation); video = {first_frame_path:"...", video_path:"..."} (video generation, i2v first-frame or kf2v first+last-frame with last_frame_path).', + "zh-CN": + 'Schema:chatml = {messages:[...]}(SFT);dpo = {messages:[...], chosen, rejected};cpt = {text:"..."}(持续预训练,原始文本);tts = {wav_fn:"train/xxx.wav", text:"..."}(音频微调);image = {img_path:"..."}(图片生成);video = {first_frame_path:"...", video_path:"..."}(视频生成,支持 i2v 首帧或通过 last_frame_path 指定 kf2v 首尾帧)。', + }, + { + "en-US": + "With no --schema, a record carrying wav_fn is validated as TTS, img_path as image, first_frame_path/video_path as video, chosen/rejected as DPO, text (no messages) as CPT, otherwise ChatML. Pass --schema to require a specific shape on every record.", + "zh-CN": + "未指定 --schema 时,包含 wav_fn 的记录按 TTS 验证,包含 img_path 的按 image 验证,包含 first_frame_path/video_path 的按 video 验证,包含 chosen/rejected 的按 DPO 验证,仅含 text(无 messages)的按 CPT 验证,其他记录按 ChatML 验证。使用 --schema 要求每条记录符合指定结构。", + }, + { + "en-US": + "ZIP archives (.zip) are validated structurally (data.jsonl present, media references resolve) in addition to per-record content checks. Use --full-validate to JSON.parse every line.", + "zh-CN": + "ZIP 归档(.zip)除逐条检查记录内容外,还会执行结构验证(存在 data.jsonl、媒体引用可解析)。使用 --full-validate 对每一行执行 JSON.parse。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/deploy/create.ts b/packages/commands/src/commands/deploy/create.ts index de468fcb..afbe6844 100644 --- a/packages/commands/src/commands/deploy/create.ts +++ b/packages/commands/src/commands/deploy/create.ts @@ -16,49 +16,76 @@ const CREATE_FLAGS = { modelName: { type: "string", valueHint: "", - description: "Model to deploy — fine-tuned output name or catalog model (required)", + description: { + "en-US": "Model to deploy — fine-tuned output name or catalog model (required)", + "zh-CN": "要部署的模型:微调输出模型名称或模型目录中的模型(必填)", + }, required: true, }, displayName: { type: "string", valueHint: "", - description: "Console display name for the deployment (required)", + description: { + "en-US": "Console display name for the deployment (required)", + "zh-CN": "部署在控制台中的显示名称(必填)", + }, required: true, }, plan: { type: "string", valueHint: "", - description: "Billing plan: lora (default, Token-billed) | ptu (Token-billed) | mu", + description: { + "en-US": "Billing plan: lora (default, Token-billed) | ptu (Token-billed) | mu", + "zh-CN": "计费方案:lora(默认,按 Token 计费)| ptu(按 Token 计费)| mu", + }, }, deploySpec: { type: "string", valueHint: "", - description: "Deploy spec (only used by plan=mu; auto-picked if omitted)", + description: { + "en-US": "Deploy spec (only used by plan=mu; auto-picked if omitted)", + "zh-CN": "部署规格(仅 plan=mu 使用;省略时自动选择)", + }, }, capacity: { type: "number", valueHint: "", - description: "Resource units (plan=mu only; required by API; defaults to the template's unit)", + description: { + "en-US": "Resource units (plan=mu only; required by API; defaults to the template's unit)", + "zh-CN": "资源单元数(仅 plan=mu;API 必填;默认为模板的单元数)", + }, }, billingMethod: { type: "string", valueHint: "", - description: 'Billing method (plan=mu only; default "POST_PAY", the only supported value)', + description: { + "en-US": 'Billing method (plan=mu only; default "POST_PAY", the only supported value)', + "zh-CN": '计费方式(仅 plan=mu;默认且仅支持 "POST_PAY")', + }, }, inputTpm: { type: "number", valueHint: "", - description: "PTU max input tokens/min (required for plan=ptu)", + description: { + "en-US": "PTU max input tokens/min (required for plan=ptu)", + "zh-CN": "PTU 每分钟最大输入 Token 数(plan=ptu 时必填)", + }, }, outputTpm: { type: "number", valueHint: "", - description: "PTU max output tokens/min (required for plan=ptu)", + description: { + "en-US": "PTU max output tokens/min (required for plan=ptu)", + "zh-CN": "PTU 每分钟最大输出 Token 数(plan=ptu 时必填)", + }, }, thinkingOutputTpm: { type: "number", valueHint: "", - description: "PTU max thinking-output tokens/min (optional, some models)", + description: { + "en-US": "PTU max thinking-output tokens/min (optional, some models)", + "zh-CN": "PTU 每分钟最大思考输出 Token 数(部分模型可选)", + }, }, } satisfies FlagsDef; @@ -66,22 +93,40 @@ const CREATE_USAGE = "--model-name --display-name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]"; const CREATE_NOTES = [ - "Plan defaults to `lora` (Token-billed) for text/image and `mu` (model-unit-", - "billed) for audio (CosyVoice TTS). Pass --plan to override.", - "For plan=ptu (Token-billed, provisioned throughput), --input-tpm and", - "--output-tpm are required (the platform rejects creation without an", - "explicit ptu_capacity despite the doc listing defaults).", - "For plan=mu, `capacity`, `billing_method` and `deploy_spec` are required.", - "billing_method defaults to POST_PAY (only supported value); deploy_spec", - "and capacity are auto-picked from GET /deployments/models when omitted.", - "Use `bl deploy models --source base` to inspect available templates.", - "After creation, status starts at PENDING and transitions to RUNNING.", - "Invoke the deployed model with: bl text chat --model ", - "NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).", - "The create response also returns a `deployed_model` field — the deployment", - "instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference", - "(`bl text chat --model `) and lifecycle commands", - "(`deploy get/scale/pause/resume/delete --deployed-model `).", + { + "en-US": + "Plan defaults to `lora` (Token-billed) for text/image and `mu` (model-unit-billed) for audio (CosyVoice TTS). Pass --plan to override.", + "zh-CN": + "文本和图片部署默认使用 `lora`(按 Token 计费),音频(CosyVoice TTS)默认使用 `mu`(按模型单元计费)。可通过 --plan 覆盖。", + }, + { + "en-US": + "For plan=ptu (Token-billed, provisioned throughput), --input-tpm and --output-tpm are required (the platform rejects creation without an explicit ptu_capacity despite the doc listing defaults).", + "zh-CN": + "plan=ptu(按 Token 计费的预置吞吐)时,--input-tpm 和 --output-tpm 必填;即使文档列出了默认值,未显式传入 ptu_capacity 时平台也会拒绝创建。", + }, + { + "en-US": + "For plan=mu, `capacity`, `billing_method` and `deploy_spec` are required. billing_method defaults to POST_PAY (only supported value); deploy_spec and capacity are auto-picked from GET /deployments/models when omitted.", + "zh-CN": + "plan=mu 时,`capacity`、`billing_method` 和 `deploy_spec` 必填。billing_method 默认且仅支持 POST_PAY;省略 deploy_spec 和 capacity 时,会从 GET /deployments/models 自动选择。", + }, + { + "en-US": "Use `bl deploy models --source base` to inspect available templates.", + "zh-CN": "使用 `bl deploy models --source base` 查看可用模板。", + }, + { + "en-US": + "After creation, status starts at PENDING and transitions to RUNNING. Invoke the deployed model with: bl text chat --model ", + "zh-CN": + "创建后状态从 PENDING 开始,随后转为 RUNNING。调用已部署模型:bl text chat --model ", + }, + { + "en-US": + "NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`). The create response also returns a `deployed_model` field — the deployment instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference (`bl text chat --model `) and lifecycle commands (`deploy get/scale/pause/resume/delete --deployed-model `).", + "zh-CN": + "注意:--model-name 是要部署的模型(例如 `qwen3-8b-ft-...`)。创建响应中的 `deployed_model` 是部署实例 ID(例如 `qwen3-8b-5ecb5f068d79`),用于推理(`bl text chat --model `)及生命周期命令(`deploy get/scale/pause/resume/delete --deployed-model `)。", + }, ]; /** @@ -157,7 +202,7 @@ async function runCreate( /** `bl deploy text create` — deploy a text model. */ export const deployTextCreate = defineCommand({ - description: "Create a text model deployment", + description: { "en-US": "Create a text model deployment", "zh-CN": "创建文本模型部署" }, auth: "apiKey", usageArgs: CREATE_USAGE, flags: CREATE_FLAGS, @@ -174,7 +219,10 @@ export const deployTextCreate = defineCommand({ /** `bl deploy audio create` — deploy an audio (TTS) model. Defaults to plan=mu. */ export const deployAudioCreate = defineCommand({ - description: "Create an audio (TTS) model deployment", + description: { + "en-US": "Create an audio (TTS) model deployment", + "zh-CN": "创建音频(TTS)模型部署", + }, auth: "apiKey", usageArgs: CREATE_USAGE, flags: CREATE_FLAGS, @@ -190,7 +238,10 @@ export const deployAudioCreate = defineCommand({ /** `bl deploy image create` — deploy an image generation model. */ export const deployImageCreate = defineCommand({ - description: "Create an image generation model deployment", + description: { + "en-US": "Create an image generation model deployment", + "zh-CN": "创建图片生成模型部署", + }, auth: "apiKey", usageArgs: CREATE_USAGE, flags: CREATE_FLAGS, diff --git a/packages/commands/src/commands/deploy/delete.ts b/packages/commands/src/commands/deploy/delete.ts index d3f386d8..bb31298a 100644 --- a/packages/commands/src/commands/deploy/delete.ts +++ b/packages/commands/src/commands/deploy/delete.ts @@ -12,12 +12,18 @@ const DELETE_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, skipPrecheck: { type: "switch", - description: "Skip the local STOPPED/FAILED status precheck", + description: { + "en-US": "Skip the local STOPPED/FAILED status precheck", + "zh-CN": "跳过本地 STOPPED/FAILED 状态预检查", + }, }, } satisfies FlagsDef; @@ -29,7 +35,10 @@ const DELETE_FLAGS = { * DELETE call. */ export default defineCommand({ - description: "Delete a model deployment (must be STOPPED or FAILED)", + description: { + "en-US": "Delete a model deployment (must be STOPPED or FAILED)", + "zh-CN": "删除模型部署(状态必须为 STOPPED 或 FAILED)", + }, auth: "apiKey", usageArgs: "--deployed-model [--skip-precheck]", flags: DELETE_FLAGS, diff --git a/packages/commands/src/commands/deploy/get.ts b/packages/commands/src/commands/deploy/get.ts index 09bff852..d2904d80 100644 --- a/packages/commands/src/commands/deploy/get.ts +++ b/packages/commands/src/commands/deploy/get.ts @@ -5,13 +5,19 @@ const GET_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Get details of a single model deployment", + description: { + "en-US": "Get details of a single model deployment", + "zh-CN": "获取单个模型部署的详情", + }, auth: "apiKey", usageArgs: "--deployed-model ", flags: GET_FLAGS, diff --git a/packages/commands/src/commands/deploy/list.ts b/packages/commands/src/commands/deploy/list.ts index 944038a5..652683b0 100644 --- a/packages/commands/src/commands/deploy/list.ts +++ b/packages/commands/src/commands/deploy/list.ts @@ -2,21 +2,31 @@ import { defineCommand, listDeployments, type FlagsDef } from "bailian-cli-core" import { emitResult } from "bailian-cli-runtime"; const LIST_FLAGS = { - page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + page: { + type: "number", + valueHint: "", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, + }, pageSize: { type: "number", valueHint: "", - description: "Results per page (default: 10, max 100)", + description: { + "en-US": "Results per page (default: 10, max 100)", + "zh-CN": "每页结果数(默认:10,最多:100)", + }, }, status: { type: "string", valueHint: "", - description: "Filter by status (PENDING / RUNNING / STOPPED / FAILED)", + description: { + "en-US": "Filter by status (PENDING / RUNNING / STOPPED / FAILED)", + "zh-CN": "按状态筛选(PENDING / RUNNING / STOPPED / FAILED)", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "List model deployments", + description: { "en-US": "List model deployments", "zh-CN": "列出模型部署" }, auth: "apiKey", usageArgs: "[--page ] [--page-size ] [--status ]", flags: LIST_FLAGS, diff --git a/packages/commands/src/commands/deploy/models.ts b/packages/commands/src/commands/deploy/models.ts index b2c81d4c..fd038fb1 100644 --- a/packages/commands/src/commands/deploy/models.ts +++ b/packages/commands/src/commands/deploy/models.ts @@ -2,27 +2,37 @@ import { defineCommand, listDeployableModels, type FlagsDef } from "bailian-cli- import { emitResult } from "bailian-cli-runtime"; const MODELS_FLAGS = { - page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + page: { + type: "number", + valueHint: "", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, + }, pageSize: { type: "number", valueHint: "", - description: "Results per page (default: 100)", + description: { "en-US": "Results per page (default: 100)", "zh-CN": "每页结果数(默认:100)" }, }, // 全局 --version 是保留 flag,目录版本过滤改名 --catalog-version。 catalogVersion: { type: "string", valueHint: "", - description: "Catalog version filter (default: v1.0; required for new catalog models)", + description: { + "en-US": "Catalog version filter (default: v1.0; required for new catalog models)", + "zh-CN": "模型目录版本筛选(默认:v1.0;新目录模型必填)", + }, }, source: { type: "string", valueHint: "", - description: "Model source filter: custom (fine-tuned) | base (catalog) | public", + description: { + "en-US": "Model source filter: custom (fine-tuned) | base (catalog) | public", + "zh-CN": "模型来源筛选:custom(微调模型)| base(模型目录)| public", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "List models available for deployment", + description: { "en-US": "List models available for deployment", "zh-CN": "列出可部署的模型" }, auth: "apiKey", usageArgs: "[--page ] [--page-size ] [--catalog-version ] [--source ]", flags: MODELS_FLAGS, diff --git a/packages/commands/src/commands/deploy/pause.ts b/packages/commands/src/commands/deploy/pause.ts index 23d36c69..99b18ce5 100644 --- a/packages/commands/src/commands/deploy/pause.ts +++ b/packages/commands/src/commands/deploy/pause.ts @@ -13,12 +13,18 @@ const PAUSE_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, skipPrecheck: { type: "switch", - description: "Skip the local RUNNING/PENDING status precheck", + description: { + "en-US": "Skip the local RUNNING/PENDING status precheck", + "zh-CN": "跳过本地 RUNNING/PENDING 状态预检查", + }, }, } satisfies FlagsDef; @@ -30,7 +36,10 @@ const PAUSE_FLAGS = { * Precheck: status must be RUNNING or PENDING. */ export default defineCommand({ - description: "Pause a running model deployment (stops billing for mu/ptu)", + description: { + "en-US": "Pause a running model deployment (stops billing for mu/ptu)", + "zh-CN": "暂停运行中的模型部署(mu/ptu 方案将停止计费)", + }, auth: "console", usageArgs: "--deployed-model [--skip-precheck]", flags: PAUSE_FLAGS, @@ -40,8 +49,18 @@ export default defineCommand({ "--deployed-model dep-... --dry-run", ], notes: [ - "While paused, billing ceases for mu/ptu plans. Use `deploy resume` to bring it back online or `deploy delete` to remove.", - "Precheck verifies status is RUNNING/PENDING before issuing the pause; pass --skip-precheck to bypass.", + { + "en-US": + "While paused, billing ceases for mu/ptu plans. Use `deploy resume` to bring it back online or `deploy delete` to remove.", + "zh-CN": + "暂停期间,mu/ptu 方案将停止计费。使用 `deploy resume` 恢复服务,或使用 `deploy delete` 删除部署。", + }, + { + "en-US": + "Precheck verifies status is RUNNING/PENDING before issuing the pause; pass --skip-precheck to bypass.", + "zh-CN": + "发起暂停前会预检查部署状态是否为 RUNNING/PENDING;可传入 --skip-precheck 跳过检查。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/deploy/resume.ts b/packages/commands/src/commands/deploy/resume.ts index 7e0cb287..d7e2e4ce 100644 --- a/packages/commands/src/commands/deploy/resume.ts +++ b/packages/commands/src/commands/deploy/resume.ts @@ -13,12 +13,18 @@ const RESUME_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, skipPrecheck: { type: "switch", - description: "Skip the local STOPPED status precheck", + description: { + "en-US": "Skip the local STOPPED status precheck", + "zh-CN": "跳过本地 STOPPED 状态预检查", + }, }, } satisfies FlagsDef; @@ -29,7 +35,10 @@ const RESUME_FLAGS = { * Precheck: status must be STOPPED. */ export default defineCommand({ - description: "Resume a paused model deployment (brings service back online)", + description: { + "en-US": "Resume a paused model deployment (brings service back online)", + "zh-CN": "恢复已暂停的模型部署(使服务重新上线)", + }, auth: "console", usageArgs: "--deployed-model [--skip-precheck]", flags: RESUME_FLAGS, @@ -39,8 +48,15 @@ export default defineCommand({ "--deployed-model dep-... --dry-run", ], notes: [ - "Precheck verifies status is STOPPED before issuing the resume; pass --skip-precheck to bypass.", - "For mu/ptu plans, billing resumes once the service is back online.", + { + "en-US": + "Precheck verifies status is STOPPED before issuing the resume; pass --skip-precheck to bypass.", + "zh-CN": "发起恢复前会预检查部署状态是否为 STOPPED;可传入 --skip-precheck 跳过检查。", + }, + { + "en-US": "For mu/ptu plans, billing resumes once the service is back online.", + "zh-CN": "对于 mu/ptu 方案,服务重新上线后将恢复计费。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/deploy/scale.ts b/packages/commands/src/commands/deploy/scale.ts index c6400a4a..c37c38eb 100644 --- a/packages/commands/src/commands/deploy/scale.ts +++ b/packages/commands/src/commands/deploy/scale.ts @@ -5,23 +5,35 @@ const SCALE_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, capacity: { type: "number", valueHint: "", - description: "New capacity in plan units (must be a multiple of base_capacity)", + description: { + "en-US": "New capacity in plan units (must be a multiple of base_capacity)", + "zh-CN": "以方案单元表示的新容量(必须是 base_capacity 的整数倍)", + }, }, inputTpm: { type: "number", valueHint: "", - description: "PTU only — input tokens per minute", + description: { + "en-US": "PTU only — input tokens per minute", + "zh-CN": "仅 PTU:每分钟输入 Token 数", + }, }, outputTpm: { type: "number", valueHint: "", - description: "PTU only — output tokens per minute", + description: { + "en-US": "PTU only — output tokens per minute", + "zh-CN": "仅 PTU:每分钟输出 Token 数", + }, }, } satisfies FlagsDef; @@ -32,7 +44,7 @@ const SCALE_FLAGS = { * integer multiple of `base_capacity` (visible via `bl deploy get`). */ export default defineCommand({ - description: "Scale a deployment's capacity", + description: { "en-US": "Scale a deployment's capacity", "zh-CN": "调整部署容量" }, auth: "apiKey", usageArgs: "--deployed-model --capacity [--input-tpm ] [--output-tpm ]", flags: SCALE_FLAGS, diff --git a/packages/commands/src/commands/deploy/update.ts b/packages/commands/src/commands/deploy/update.ts index 6c3ab896..326feb20 100644 --- a/packages/commands/src/commands/deploy/update.ts +++ b/packages/commands/src/commands/deploy/update.ts @@ -5,18 +5,21 @@ const UPDATE_FLAGS = { deployedModel: { type: "string", valueHint: "", - description: "Deployed model identifier (required)", + description: { + "en-US": "Deployed model identifier (required)", + "zh-CN": "已部署模型标识(必填)", + }, required: true, }, rpmLimit: { type: "number", valueHint: "", - description: "Requests per minute", + description: { "en-US": "Requests per minute", "zh-CN": "每分钟请求数" }, }, tpmLimit: { type: "number", valueHint: "", - description: "Tokens per minute", + description: { "en-US": "Tokens per minute", "zh-CN": "每分钟 Token 数" }, }, } satisfies FlagsDef; @@ -27,7 +30,10 @@ const UPDATE_FLAGS = { * Body: at least one of `rpm_limit` (requests/min) or `tpm_limit` (tokens/min). */ export default defineCommand({ - description: "Update a deployment's rate limits (rpm_limit / tpm_limit)", + description: { + "en-US": "Update a deployment's rate limits (rpm_limit / tpm_limit)", + "zh-CN": "更新部署的限流配置(rpm_limit / tpm_limit)", + }, auth: "apiKey", usageArgs: "--deployed-model [--rpm-limit ] [--tpm-limit ]", flags: UPDATE_FLAGS, @@ -35,7 +41,12 @@ export default defineCommand({ "--deployed-model dep-... --rpm-limit 1000", "--deployed-model dep-... --rpm-limit 1000 --tpm-limit 200000", ], - notes: ["At least one of --rpm-limit / --tpm-limit must be provided."], + notes: [ + { + "en-US": "At least one of --rpm-limit / --tpm-limit must be provided.", + "zh-CN": "--rpm-limit / --tpm-limit 至少需要提供一个。", + }, + ], validate: (flags) => flags.rpmLimit === undefined && flags.tpmLimit === undefined ? "Provide at least one of --rpm-limit / --tpm-limit." diff --git a/packages/commands/src/commands/file/upload.ts b/packages/commands/src/commands/file/upload.ts index e655beb1..5030810d 100644 --- a/packages/commands/src/commands/file/upload.ts +++ b/packages/commands/src/commands/file/upload.ts @@ -2,20 +2,29 @@ import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; export default defineCommand({ - description: "Upload a local file to DashScope temporary storage (48h)", + description: { + "en-US": "Upload a local file to DashScope temporary storage (48h)", + "zh-CN": "将本地文件上传到 DashScope 临时存储(保留 48 小时)", + }, auth: "apiKey", usageArgs: "--file --model ", flags: { file: { type: "string", valueHint: "", - description: "Local file to upload (image, video, audio)", + description: { + "en-US": "Local file to upload (image, video, audio)", + "zh-CN": "要上传的本地文件(图片、视频或音频)", + }, required: true, }, model: { type: "string", valueHint: "", - description: "Target model name (file is bound to this model)", + description: { + "en-US": "Target model name (file is bound to this model)", + "zh-CN": "目标模型名称(文件将与该模型绑定)", + }, required: true, }, }, diff --git a/packages/commands/src/commands/finetune/cancel.ts b/packages/commands/src/commands/finetune/cancel.ts index 966ce3e7..4fb7a055 100644 --- a/packages/commands/src/commands/finetune/cancel.ts +++ b/packages/commands/src/commands/finetune/cancel.ts @@ -5,20 +5,24 @@ const CANCEL_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Cancel a running fine-tune job", + description: { "en-US": "Cancel a running fine-tune job", "zh-CN": "取消正在运行的微调任务" }, auth: "apiKey", usageArgs: "--job-id ", flags: CANCEL_FLAGS, exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --dry-run"], notes: [ - "Only PENDING / RUNNING jobs can be cancelled. Completed / failed / already-", - "cancelled jobs return a server-side error (passed through verbatim).", + { + "en-US": + "Only PENDING / RUNNING jobs can be cancelled. Completed / failed / already-cancelled jobs return a server-side error (passed through verbatim).", + "zh-CN": + "只有 PENDING / RUNNING 状态的任务可以取消。已完成、失败或已取消的任务会返回服务端错误(原样透传)。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/finetune/capability.ts b/packages/commands/src/commands/finetune/capability.ts index 3ba704ae..024ec09c 100644 --- a/packages/commands/src/commands/finetune/capability.ts +++ b/packages/commands/src/commands/finetune/capability.ts @@ -30,18 +30,27 @@ const CAPABILITY_FLAGS = { baseModel: { type: "string", valueHint: "", - description: "List training types supported by this base model.", + description: { + "en-US": "List training types supported by this base model.", + "zh-CN": "列出该基础模型支持的训练类型。", + }, }, trainingType: { type: "string", valueHint: "", - description: `List models supporting this training type: ${TRAINING_TYPES_CLI.join(" | ")}.`, + description: { + "en-US": `List models supporting this training type: ${TRAINING_TYPES_CLI.join(" | ")}.`, + "zh-CN": `列出支持该训练类型的模型:${TRAINING_TYPES_CLI.join(" | ")}。`, + }, }, } satisfies FlagsDef; export default defineCommand({ - description: - "Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it)", + description: { + "en-US": + "Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it)", + "zh-CN": "查询微调训练能力:按模型查询其支持的训练类型,或按训练类型查询支持它的模型", + }, auth: "none", usageArgs: "--base-model | --training-type ", flags: CAPABILITY_FLAGS, @@ -52,10 +61,20 @@ export default defineCommand({ "--training-type sft --quiet", ], notes: [ - "Exactly one of --base-model / --training-type is required.", - "Training-type values use the `` / `-lora` convention:", - "sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)", - "Queries listFoundationModels, a public API — no console login needed.", + { + "en-US": "Exactly one of --base-model / --training-type is required.", + "zh-CN": "--base-model 和 --training-type 必须且只能指定一个。", + }, + { + "en-US": + "Training-type values use the `` / `-lora` convention: sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)", + "zh-CN": + "训练类型遵循 `` / `-lora` 命名约定:sft | sft-lora | dpo | dpo-lora | cpt。(服务端没有 cpt-lora 变体。)", + }, + { + "en-US": "Queries listFoundationModels, a public API — no console login needed.", + "zh-CN": "查询公开 API listFoundationModels,无需登录控制台。", + }, ], validate: (f) => { if (f.baseModel && f.trainingType) diff --git a/packages/commands/src/commands/finetune/checkpoints.ts b/packages/commands/src/commands/finetune/checkpoints.ts index 190110d6..d8f02dfa 100644 --- a/packages/commands/src/commands/finetune/checkpoints.ts +++ b/packages/commands/src/commands/finetune/checkpoints.ts @@ -5,7 +5,7 @@ const CHECKPOINTS_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, } satisfies FlagsDef; @@ -13,14 +13,26 @@ const CHECKPOINTS_FLAGS = { const EXPIRY_WARN_THRESHOLD_MS = 72 * 60 * 60 * 1000; // 72 hours export default defineCommand({ - description: "List checkpoints produced by a fine-tune job", + description: { + "en-US": "List checkpoints produced by a fine-tune job", + "zh-CN": "列出微调任务生成的 Checkpoint", + }, auth: "apiKey", usageArgs: "--job-id ", flags: CHECKPOINTS_FLAGS, exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --output json"], notes: [ - "`model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.", - "Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.", + { + "en-US": + "`model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.", + "zh-CN": + "SUCCEEDED Checkpoint 中显示的 `model_name` 可直接作为 `deploy create --model-name` 的输入。", + }, + { + "en-US": + "Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.", + "zh-CN": "Checkpoint 创建后约 15 天过期,`expire_time` 显示截止时间。请在过期前导出或部署。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/finetune/create.ts b/packages/commands/src/commands/finetune/create.ts index 834168c0..2fe67df5 100644 --- a/packages/commands/src/commands/finetune/create.ts +++ b/packages/commands/src/commands/finetune/create.ts @@ -218,31 +218,47 @@ const COMMON_FLAGS = { baseModel: { type: "string", valueHint: "", - description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)", + description: { + "en-US": "Base model to fine-tune (e.g. qwen3-8b; not the output model name)", + "zh-CN": "要微调的基础模型(例如 qwen3-8b;不是输出模型名称)", + }, required: true, }, datasets: { type: "string", valueHint: "", - description: - "Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used.", + description: { + "en-US": + "Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image/video). Local paths are uploaded (validated) first, then their file-ids are used.", + "zh-CN": + "数据集文件 ID 或本地路径,以逗号分隔(文本使用 .jsonl,音频、图片和视频使用 .zip)。本地路径会先验证并上传,再使用对应的 file-id。", + }, required: true, }, validations: { type: "string", valueHint: "", - description: - "Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets).", + description: { + "en-US": + "Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets).", + "zh-CN": "验证数据集文件 ID 或本地路径,以逗号分隔(与 --datasets 一样自动上传)。", + }, }, modelName: { type: "string", valueHint: "", - description: "Output model name (after training)", + description: { + "en-US": "Output model name (after training)", + "zh-CN": "训练完成后的输出模型名称", + }, }, suffix: { type: "string", valueHint: "", - description: "Output suffix appended by the platform (finetuned_output_suffix)", + description: { + "en-US": "Output suffix appended by the platform (finetuned_output_suffix)", + "zh-CN": "平台追加的输出后缀(finetuned_output_suffix)", + }, }, } satisfies FlagsDef; @@ -257,28 +273,37 @@ const TEXT_FLAGS = { trainingType: { type: "string", valueHint: "", - description: `Training type: ${TRAINING_TYPES_CLI.join(" | ")} (default: ${DEFAULT_TRAINING_TYPE}). Mapping to the server happens at the interface boundary (e.g. sft-lora -> efficient_sft, dpo -> dpo_full).`, + description: { + "en-US": `Training type: ${TRAINING_TYPES_CLI.join(" | ")} (default: ${DEFAULT_TRAINING_TYPE}). Mapping to the server happens at the interface boundary (e.g. sft-lora -> efficient_sft, dpo -> dpo_full).`, + "zh-CN": `训练类型:${TRAINING_TYPES_CLI.join(" | ")}(默认:${DEFAULT_TRAINING_TYPE})。在接口边界转换为服务端值(例如 sft-lora -> efficient_sft、dpo -> dpo_full)。`, + }, }, nEpochs: { type: "number", valueHint: "", - description: "Number of epochs (default: 3)", + description: { "en-US": "Number of epochs (default: 3)", "zh-CN": "训练轮数(默认:3)" }, }, batchSize: { type: "number", valueHint: "", - description: - "Per-device batch size (clamped to [8, 1024]). Auto-set to 8 for small datasets (<100KB)", + description: { + "en-US": + "Per-device batch size (clamped to [8, 1024]). Auto-set to 8 for small datasets (<100KB)", + "zh-CN": "单设备 Batch Size(限制在 [8, 1024])。小数据集(<100KB)自动设为 8", + }, }, learningRate: { type: "string", valueHint: "", - description: 'Learning rate as a string to preserve precision (e.g. "1.6e-5")', + description: { + "en-US": 'Learning rate as a string to preserve precision (e.g. "1.6e-5")', + "zh-CN": '以字符串形式指定学习率以保留精度(例如 "1.6e-5")', + }, }, maxLength: { type: "number", valueHint: "", - description: "Max sequence length", + description: { "en-US": "Max sequence length", "zh-CN": "最大序列长度" }, }, } satisfies FlagsDef; @@ -305,13 +330,20 @@ const IMAGE_FLAGS = { type: "string", choices: ["t2i", "i2i"] as const, valueHint: "", - description: - "Generation type: t2i (default) | i2i. Sets generation_type/max_pixels. Required to train I2I from a file-id or with --dry-run (local data auto-detects input_img).", + description: { + "en-US": + "Generation type: t2i (default) | i2i. Sets generation_type/max_pixels. Required to train I2I from a file-id or with --dry-run (local data auto-detects input_img).", + "zh-CN": + "生成类型:t2i(默认)| i2i。用于设置 generation_type/max_pixels。通过 file-id 训练 I2I 或使用 --dry-run 时必填(本地数据会自动识别 input_img)。", + }, }, learningRate: { type: "string", valueHint: "", - description: 'Learning rate as a string to preserve precision (e.g. "3e-5")', + description: { + "en-US": 'Learning rate as a string to preserve precision (e.g. "3e-5")', + "zh-CN": '以字符串形式指定学习率以保留精度(例如 "3e-5")', + }, }, } satisfies FlagsDef; @@ -335,17 +367,23 @@ const VIDEO_FLAGS = { nEpochs: { type: "number", valueHint: "", - description: "Training epochs (default: 50)", + description: { "en-US": "Training epochs (default: 50)", "zh-CN": "训练轮数(默认:50)" }, }, batchSize: { type: "number", valueHint: "", - description: "Batch size (default: model-specific, 1 for wan2.7, 4 for wan2.5/2.2)", + description: { + "en-US": "Batch size (default: model-specific, 1 for wan2.7, 4 for wan2.5/2.2)", + "zh-CN": "Batch Size(默认值因模型而异:wan2.7 为 1,wan2.5/2.2 为 4)", + }, }, learningRate: { type: "string", valueHint: "", - description: 'Learning rate as a string to preserve precision (default: "2e-5")', + description: { + "en-US": 'Learning rate as a string to preserve precision (default: "2e-5")', + "zh-CN": '以字符串形式指定学习率以保留精度(默认:"2e-5")', + }, }, } satisfies FlagsDef; @@ -353,43 +391,70 @@ const VIDEO_USAGE = "--base-model --datasets [--validations ] [--model-name ] [--suffix ] [--n-epochs ] [--batch-size ] [--learning-rate ]"; const COMMON_NOTES = [ - "Creating a job uploads any local datasets and consumes training quota.", - "Use --dry-run to preview the request body without submitting.", - "--datasets / --validations accept either file-ids (from `dataset upload`)", - "or local paths. Local paths are validated and uploaded first, then their", - "file-ids are submitted — a one-step upload-and-train.", + { + "en-US": "Creating a job uploads any local datasets and consumes training quota.", + "zh-CN": "创建任务会上传所有本地数据集并消耗训练额度。", + }, + { + "en-US": "Use --dry-run to preview the request body without submitting.", + "zh-CN": "使用 --dry-run 预览请求体,不实际提交。", + }, + { + "en-US": + "--datasets / --validations accept either file-ids (from `dataset upload`) or local paths. Local paths are validated and uploaded first, then their file-ids are submitted — a one-step upload-and-train.", + "zh-CN": + "--datasets / --validations 可接受 file-id(来自 `dataset upload`)或本地路径。本地路径会先验证并上传,再提交对应的 file-id,实现一步上传并训练。", + }, ]; const TEXT_NOTES = [ ...COMMON_NOTES, - "Training-type values use the `` / `-lora` convention:", - "sft (full) | sft-lora (LoRA) | dpo (full) | dpo-lora (LoRA) | cpt. These map", - "to the server's training_type at the interface boundary, so the rest of the", - "CLI never sees the raw server strings.", - "Before submitting (non dry-run) the job, the model's training capability is", - "checked via listFoundationModels (no console login required); an unsupported", - "training type fails fast with the list the model actually supports.", - "n_epochs defaults to 3. Other hyper-parameters are platform defaults unless set.", - "Learning rate is forwarded as a string to avoid JSON-number precision loss.", - "Pre-submit gate: if the training dataset's sample count is not greater", - "than batch_size, the job is rejected before upload or quota consumption", - "(the platform would otherwise fail ~10 min in, after data processing).", + { + "en-US": + "Training-type values use the `` / `-lora` convention: sft (full) | sft-lora (LoRA) | dpo (full) | dpo-lora (LoRA) | cpt. These map to the server's training_type at the interface boundary, so the rest of the CLI never sees the raw server strings.", + "zh-CN": + "训练类型遵循 `` / `-lora` 命名约定:sft(全量)| sft-lora(LoRA)| dpo(全量)| dpo-lora(LoRA)| cpt。这些值会在接口边界映射为服务端 training_type,因此 CLI 的其他部分不会接触服务端原始字符串。", + }, + { + "en-US": + "Before submitting (non dry-run) the job, the model's training capability is checked via listFoundationModels (no console login required); an unsupported training type fails fast with the list the model actually supports.", + "zh-CN": + "提交任务前(非 dry-run),会通过 listFoundationModels 检查模型训练能力(无需登录控制台);如果训练类型不受支持,会立即失败并列出该模型实际支持的训练类型。", + }, + { + "en-US": "n_epochs defaults to 3. Other hyper-parameters are platform defaults unless set.", + "zh-CN": "n_epochs 默认为 3。其他超参数未设置时使用平台默认值。", + }, + { + "en-US": "Learning rate is forwarded as a string to avoid JSON-number precision loss.", + "zh-CN": "学习率以字符串形式传递,避免 JSON 数字精度损失。", + }, + { + "en-US": + "Pre-submit gate: if the training dataset's sample count is not greater than batch_size, the job is rejected before upload or quota consumption (the platform would otherwise fail ~10 min in, after data processing).", + "zh-CN": + "提交前检查:如果训练数据集的样本数不大于 batch_size,会在上传或消耗额度前拒绝任务(否则平台会在数据处理约 10 分钟后才失败)。", + }, ]; const AUDIO_NOTES = [ ...COMMON_NOTES, - "Audio TTS training runs sft-lora (efficient_sft) with fixed CosyVoice", - "hyper-parameter defaults; there are no training-type or hyper-parameter", - "knobs to set.", + { + "en-US": + "Audio TTS training runs sft-lora (efficient_sft) with fixed CosyVoice hyper-parameter defaults; there are no training-type or hyper-parameter knobs to set.", + "zh-CN": + "音频 TTS 训练使用 sft-lora(efficient_sft)和固定的 CosyVoice 超参数默认值;没有可设置的训练类型或超参数选项。", + }, ]; const IMAGE_NOTES = [ ...COMMON_NOTES, - "Image generation training runs sft-lora (efficient_sft) with fixed defaults;", - "only --learning-rate is overridable. T2I vs I2I is declared with", - "--generation-type (default t2i), which sets generation_type/max_pixels. For", - "local data the type is auto-detected (records with input_img train I2I);", - "pass --generation-type explicitly to train I2I from a file-id or in --dry-run.", + { + "en-US": + "Image generation training runs sft-lora (efficient_sft) with fixed defaults; only --learning-rate is overridable. T2I vs I2I is declared with --generation-type (default t2i), which sets generation_type/max_pixels. For local data the type is auto-detected (records with input_img train I2I); pass --generation-type explicitly to train I2I from a file-id or in --dry-run.", + "zh-CN": + "图片生成训练使用 sft-lora(efficient_sft)和固定默认值;仅 --learning-rate 可覆盖。通过 --generation-type(默认 t2i)声明 T2I 或 I2I,并设置 generation_type/max_pixels。本地数据会自动识别类型(包含 input_img 的记录训练 I2I);通过 file-id 训练 I2I 或使用 --dry-run 时,请显式传入 --generation-type。", + }, ]; /** @@ -663,7 +728,10 @@ async function runCreate( /** `bl finetune text create` — fine-tune a text model. Datasets are `.jsonl`. */ export const finetuneTextCreate = defineCommand({ - description: "Create a text model fine-tune job (sft | sft-lora | dpo | dpo-lora | cpt)", + description: { + "en-US": "Create a text model fine-tune job (sft | sft-lora | dpo | dpo-lora | cpt)", + "zh-CN": "创建文本模型微调任务(sft | sft-lora | dpo | dpo-lora | cpt)", + }, auth: "apiKey", usageArgs: TEXT_USAGE, flags: TEXT_FLAGS, @@ -683,7 +751,10 @@ export const finetuneTextCreate = defineCommand({ /** `bl finetune audio create` — fine-tune an audio TTS model. Datasets are `.zip`. */ export const finetuneAudioCreate = defineCommand({ - description: "Create an audio TTS model fine-tune job (sft-lora)", + description: { + "en-US": "Create an audio TTS model fine-tune job (sft-lora)", + "zh-CN": "创建音频 TTS 模型微调任务(sft-lora)", + }, auth: "apiKey", usageArgs: AUDIO_USAGE, flags: AUDIO_FLAGS, @@ -700,7 +771,10 @@ export const finetuneAudioCreate = defineCommand({ /** `bl finetune image create` — fine-tune an image generation model. Datasets are `.zip`. */ export const finetuneImageCreate = defineCommand({ - description: "Create an image generation model fine-tune job (sft-lora)", + description: { + "en-US": "Create an image generation model fine-tune job (sft-lora)", + "zh-CN": "创建图片生成模型微调任务(sft-lora)", + }, auth: "apiKey", usageArgs: IMAGE_USAGE, flags: IMAGE_FLAGS, @@ -718,16 +792,28 @@ export const finetuneImageCreate = defineCommand({ const VIDEO_NOTES = [ ...COMMON_NOTES, - "Video generation training (Wan i2v/kf2v) runs efficient_sft with model-", - "specific defaults: wan2.7 (batch_size=1, max_pixels=102400), wan2.5/2.2", - "(batch_size=4, max_pixels per model). Override with --batch-size/--n-epochs.", - "Datasets are .zip archives with data.jsonl + frame images + videos.", - "Recommended: ≥10 training samples, 20-100 for stable results.", + { + "en-US": + "Video generation training (Wan i2v/kf2v) runs efficient_sft with model-specific defaults: wan2.7 (batch_size=1, max_pixels=102400), wan2.5/2.2 (batch_size=4, max_pixels per model). Override with --batch-size/--n-epochs.", + "zh-CN": + "视频生成训练(Wan i2v/kf2v)使用 efficient_sft 和模型专属默认值:wan2.7(batch_size=1、max_pixels=102400),wan2.5/2.2(batch_size=4,max_pixels 因模型而异)。可通过 --batch-size/--n-epochs 覆盖。", + }, + { + "en-US": "Datasets are .zip archives with data.jsonl + frame images + videos.", + "zh-CN": "数据集为包含 data.jsonl、帧图片和视频的 .zip 归档。", + }, + { + "en-US": "Recommended: ≥10 training samples, 20-100 for stable results.", + "zh-CN": "建议至少准备 10 个训练样本;20–100 个样本可获得更稳定的效果。", + }, ]; /** `bl finetune video create` — fine-tune a video generation model. Datasets are `.zip`. */ export const finetuneVideoCreate = defineCommand({ - description: "Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft)", + description: { + "en-US": "Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft)", + "zh-CN": "创建视频生成模型微调任务(Wan i2v/kf2v,efficient_sft)", + }, auth: "apiKey", usageArgs: VIDEO_USAGE, flags: VIDEO_FLAGS, diff --git a/packages/commands/src/commands/finetune/delete.ts b/packages/commands/src/commands/finetune/delete.ts index f18a13f7..c5d7c8f9 100644 --- a/packages/commands/src/commands/finetune/delete.ts +++ b/packages/commands/src/commands/finetune/delete.ts @@ -5,20 +5,23 @@ const DELETE_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Delete a fine-tune job record", + description: { "en-US": "Delete a fine-tune job record", "zh-CN": "删除微调任务记录" }, auth: "apiKey", usageArgs: "--job-id ", flags: DELETE_FLAGS, exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --dry-run"], notes: [ - "Cancel a RUNNING job first via `finetune cancel` — the platform refuses", - "to delete jobs that are still in flight.", + { + "en-US": + "Cancel a RUNNING job first via `finetune cancel` — the platform refuses to delete jobs that are still in flight.", + "zh-CN": "请先通过 `finetune cancel` 取消 RUNNING 任务,平台拒绝删除仍在运行的任务。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/finetune/export.ts b/packages/commands/src/commands/finetune/export.ts index 97bb0aa7..f753b636 100644 --- a/packages/commands/src/commands/finetune/export.ts +++ b/packages/commands/src/commands/finetune/export.ts @@ -5,33 +5,42 @@ const EXPORT_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, checkpoint: { type: "string", valueHint: "", - description: "Checkpoint identifier from `finetune checkpoints` (required)", + description: { + "en-US": "Checkpoint identifier from `finetune checkpoints` (required)", + "zh-CN": "来自 `finetune checkpoints` 的 Checkpoint 标识(必填)", + }, required: true, }, modelName: { type: "string", valueHint: "", - description: "Deployable model name (required)", + description: { "en-US": "Deployable model name (required)", "zh-CN": "可部署模型名称(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Publish a checkpoint as a deployable model", + description: { + "en-US": "Publish a checkpoint as a deployable model", + "zh-CN": "将 Checkpoint 发布为可部署模型", + }, auth: "apiKey", usageArgs: "--job-id --checkpoint --model-name ", flags: EXPORT_FLAGS, exampleArgs: ["--job-id ft-xxx --checkpoint ckpt-3 --model-name my-qwen-sft"], notes: [ - "Required before `deploy create` can target a checkpoint. The", - "platform may auto-export the best checkpoint when a job reaches SUCCEEDED —", - "explicit export is the canonical path for non-best checkpoints.", + { + "en-US": + "Required before `deploy create` can target a checkpoint. The platform may auto-export the best checkpoint when a job reaches SUCCEEDED — explicit export is the canonical path for non-best checkpoints.", + "zh-CN": + "必须先执行此操作,`deploy create` 才能使用 Checkpoint。任务达到 SUCCEEDED 后,平台可能自动导出最佳 Checkpoint;对于非最佳 Checkpoint,显式导出是标准方式。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/finetune/get.ts b/packages/commands/src/commands/finetune/get.ts index 0ec4d5a9..3be579f5 100644 --- a/packages/commands/src/commands/finetune/get.ts +++ b/packages/commands/src/commands/finetune/get.ts @@ -6,13 +6,16 @@ const GET_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, } satisfies FlagsDef; export default defineCommand({ - description: "Get details of a single fine-tune job", + description: { + "en-US": "Get details of a single fine-tune job", + "zh-CN": "获取单个微调任务的详情", + }, auth: "apiKey", usageArgs: "--job-id ", flags: GET_FLAGS, diff --git a/packages/commands/src/commands/finetune/list.ts b/packages/commands/src/commands/finetune/list.ts index d37fc5be..e5c08a86 100644 --- a/packages/commands/src/commands/finetune/list.ts +++ b/packages/commands/src/commands/finetune/list.ts @@ -2,26 +2,39 @@ import { defineCommand, listFineTunes, type FlagsDef } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; const LIST_FLAGS = { - page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + page: { + type: "number", + valueHint: "", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, + }, pageSize: { type: "number", valueHint: "", - description: "Results per page (default: 10, max 100)", + description: { + "en-US": "Results per page (default: 10, max 100)", + "zh-CN": "每页结果数(默认:10,最多:100)", + }, }, status: { type: "string", valueHint: "", - description: "Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)", + description: { + "en-US": "Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)", + "zh-CN": "按状态筛选(PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)", + }, }, baseModel: { type: "string", valueHint: "", - description: "Filter by base model ID (server-side)", + description: { + "en-US": "Filter by base model ID (server-side)", + "zh-CN": "按基础模型 ID 筛选(服务端筛选)", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "List fine-tune jobs", + description: { "en-US": "List fine-tune jobs", "zh-CN": "列出微调任务" }, auth: "apiKey", usageArgs: "[--page ] [--page-size ] [--status ] [--base-model ]", flags: LIST_FLAGS, diff --git a/packages/commands/src/commands/finetune/logs.ts b/packages/commands/src/commands/finetune/logs.ts index 320c07cb..82c1232e 100644 --- a/packages/commands/src/commands/finetune/logs.ts +++ b/packages/commands/src/commands/finetune/logs.ts @@ -66,31 +66,47 @@ const LOGS_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, - page: { type: "number", valueHint: "", description: "Page number (default: 1)" }, + page: { + type: "number", + valueHint: "", + description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" }, + }, pageSize: { type: "number", valueHint: "", - description: "Lines per page (default: server-defined)", + description: { + "en-US": "Lines per page (default: server-defined)", + "zh-CN": "每页行数(默认:由服务端决定)", + }, }, search: { type: "string", valueHint: "", - description: - "Case-insensitive substring filter. When set, all log pages are fetched and filtered client-side (--page is ignored).", + description: { + "en-US": + "Case-insensitive substring filter. When set, all log pages are fetched and filtered client-side (--page is ignored).", + "zh-CN": "不区分大小写的子字符串筛选。设置后会获取全部日志页并在客户端筛选(忽略 --page)。", + }, }, tail: { type: "number", valueHint: "", - description: - "Keep only the last N entries. When set, all log pages are fetched and the trailing N are kept (--page is ignored).", + description: { + "en-US": + "Keep only the last N entries. When set, all log pages are fetched and the trailing N are kept (--page is ignored).", + "zh-CN": "仅保留最后 N 条记录。设置后会获取全部日志页并保留末尾 N 条(忽略 --page)。", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: "Fetch training logs for a fine-tune job", + description: { + "en-US": "Fetch training logs for a fine-tune job", + "zh-CN": "获取微调任务的训练日志", + }, auth: "apiKey", usageArgs: "--job-id [--page ] [--page-size ] [--search ] [--tail ]", flags: LOGS_FLAGS, diff --git a/packages/commands/src/commands/finetune/price.ts b/packages/commands/src/commands/finetune/price.ts index 32dd7562..e24c59a7 100644 --- a/packages/commands/src/commands/finetune/price.ts +++ b/packages/commands/src/commands/finetune/price.ts @@ -13,24 +13,36 @@ const PRICE_FLAGS = { baseModel: { type: "string", valueHint: "", - description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)", + description: { + "en-US": "Base model to fine-tune (e.g. qwen3-8b; not the output model name)", + "zh-CN": "待微调的基座模型(例如 qwen3-8b;不是输出模型名称)", + }, required: true, }, datasets: { type: "string", valueHint: "", - description: "Training dataset file IDs, comma-separated (required)", + description: { + "en-US": "Training dataset file IDs, comma-separated (required)", + "zh-CN": "训练数据集文件 ID,多个以逗号分隔(必填)", + }, required: true, }, trainingType: { type: "string", valueHint: "", - description: "Training type: sft | dpo | cpt (default: sft)", + description: { + "en-US": "Training type: sft | dpo | cpt (default: sft)", + "zh-CN": "训练类型:sft | dpo | cpt(默认:sft)", + }, }, nEpochs: { type: "number", valueHint: "", - description: "Number of training epochs (default: 3)", + description: { + "en-US": "Number of training epochs (default: 3)", + "zh-CN": "训练轮数(默认:3)", + }, }, } satisfies FlagsDef; @@ -44,7 +56,10 @@ const ESTIMATE_MAX_LENGTH = 8192; const DEFAULT_N_EPOCHS = 3; export default defineCommand({ - description: "Estimate the training cost for a fine-tune job (token billing)", + description: { + "en-US": "Estimate the training cost for a fine-tune job (token billing)", + "zh-CN": "估算微调任务的训练费用(按 Token 计费)", + }, auth: "console", usageArgs: "--base-model --datasets [--training-type ] [--n-epochs ]", flags: PRICE_FLAGS, @@ -54,9 +69,21 @@ export default defineCommand({ "--base-model qwen3-8b --datasets file-ft-xxx --training-type cpt", ], notes: [ - "Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.", - "Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.", - "Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.", + { + "en-US": + "Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.", + "zh-CN": "该结果仅为估算值——服务端根据数据集计算 Token 用量,最终费用以账单为准。", + }, + { + "en-US": + "Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.", + "zh-CN": "支持估算 sft / dpo / cpt 的 Token 计费;此命令不支持训练单元(MTU)计费估算。", + }, + { + "en-US": + "Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.", + "zh-CN": "除 --n-epochs 外,其他超参数会使用具有代表性的固定默认值进行估算。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/finetune/watch.ts b/packages/commands/src/commands/finetune/watch.ts index eb802a5d..a4fad6eb 100644 --- a/packages/commands/src/commands/finetune/watch.ts +++ b/packages/commands/src/commands/finetune/watch.ts @@ -52,50 +52,84 @@ const WATCH_FLAGS = { jobId: { type: "string", valueHint: "", - description: "Fine-tune job ID (required)", + description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" }, required: true, }, follow: { type: "switch", - description: - "Block and poll until a terminal state (the legacy behavior). Without it, a single status probe is performed and the command returns immediately.", + description: { + "en-US": + "Block and poll until a terminal state (the legacy behavior). Without it, a single status probe is performed and the command returns immediately.", + "zh-CN": "阻塞并轮询至终态(旧版行为)。不使用时仅查询一次状态并立即返回。", + }, }, interval: { type: "number", valueHint: "", - description: `Seconds between polls with --follow (default: ${DEFAULT_INTERVAL_SEC}, min: ${MIN_INTERVAL_SEC}). Ignored without --follow.`, + description: { + "en-US": `Seconds between polls with --follow (default: ${DEFAULT_INTERVAL_SEC}, min: ${MIN_INTERVAL_SEC}). Ignored without --follow.`, + "zh-CN": `使用 --follow 时的轮询间隔秒数(默认:${DEFAULT_INTERVAL_SEC},最小:${MIN_INTERVAL_SEC})。未使用 --follow 时忽略。`, + }, }, pollTimeout: { type: "number", valueHint: "", - description: - "With --follow, stop polling after this many seconds (default: no limit). Ignored without --follow.", + description: { + "en-US": + "With --follow, stop polling after this many seconds (default: no limit). Ignored without --follow.", + "zh-CN": "使用 --follow 时,在指定秒数后停止轮询(默认:无限制)。未使用 --follow 时忽略。", + }, }, } satisfies FlagsDef; export default defineCommand({ - description: - "Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal.", + description: { + "en-US": + "Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal.", + "zh-CN": "查询微调任务状态(默认:单次非阻塞获取)。使用 --follow 持续轮询至终态。", + }, auth: "apiKey", usageArgs: "--job-id [--follow] [--interval ] [--poll-timeout ]", flags: WATCH_FLAGS, exampleArgs: [ - "--job-id ft-xxx # single probe, returns immediately", - "--job-id ft-xxx --output json # status probe for agents", - "--job-id ft-xxx --follow # block until terminal", + { + "en-US": "--job-id ft-xxx # single probe, returns immediately", + "zh-CN": "--job-id ft-xxx # 单次查询,立即返回", + }, + { + "en-US": "--job-id ft-xxx --output json # status probe for agents", + "zh-CN": "--job-id ft-xxx --output json # 供智能体查询状态", + }, + { + "en-US": "--job-id ft-xxx --follow # block until terminal", + "zh-CN": "--job-id ft-xxx --follow # 阻塞等待至终态", + }, "--job-id ft-xxx --follow --interval 5", "--job-id ft-xxx --follow --poll-timeout 3600", ], notes: [ - "Default (no --follow) is a NON-BLOCKING single status probe: one fetch, then", - "return immediately. This is the mode meant for agents / scripts — the caller", - "owns the polling cadence, so the CLI never holds the terminal.", - "A terminal FAILED/CANCELED status raises a normal CLI error (non-zero exit);", - "a SUCCEEDED or still-running status returns 0. With --follow, exceeding", - "--poll-timeout raises a timeout error.", - "Use --follow for the blocking, human-terminal-follow experience; use the", - "default mode when driving the loop yourself (e.g. from an agent).", - "For per-step training output (not status), use `finetune logs`.", + { + "en-US": + "Default (no --follow) is a NON-BLOCKING single status probe: one fetch, then return immediately. This is the mode meant for agents / scripts — the caller owns the polling cadence, so the CLI never holds the terminal.", + "zh-CN": + "默认不使用 --follow,执行非阻塞的单次状态查询:获取一次后立即返回。该模式适用于 Agent / 脚本,由调用方控制轮询节奏,CLI 不会持续占用终端。", + }, + { + "en-US": + "A terminal FAILED/CANCELED status raises a normal CLI error (non-zero exit); a SUCCEEDED or still-running status returns 0. With --follow, exceeding --poll-timeout raises a timeout error.", + "zh-CN": + "终态 FAILED/CANCELED 会触发普通 CLI 错误(非零退出码);SUCCEEDED 或仍在运行时返回 0。使用 --follow 时,超过 --poll-timeout 会触发超时错误。", + }, + { + "en-US": + "Use --follow for the blocking, human-terminal-follow experience; use the default mode when driving the loop yourself (e.g. from an agent).", + "zh-CN": + "需要在人工终端中阻塞跟踪时使用 --follow;自行驱动轮询(例如通过 Agent)时使用默认模式。", + }, + { + "en-US": "For per-step training output (not status), use `finetune logs`.", + "zh-CN": "要查看逐步骤训练输出(而非状态),请使用 `finetune logs`。", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/image/edit.ts b/packages/commands/src/commands/image/edit.ts index e0bfcc23..466b256d 100644 --- a/packages/commands/src/commands/image/edit.ts +++ b/packages/commands/src/commands/image/edit.ts @@ -35,41 +35,67 @@ const EDIT_FLAGS = { image: { type: "array", valueHint: "", - description: "Source image URL or local file path (repeatable for multi-image merge)", + description: { + "en-US": "Source image URL or local file path (repeatable for multi-image merge)", + "zh-CN": "源图片 URL 或本地文件路径(多图融合时可重复)", + }, required: true, }, prompt: { type: "string", valueHint: "", - description: "Edit instruction text", + description: { "en-US": "Edit instruction text", "zh-CN": "编辑指令文本" }, required: true, }, model: { type: "string", valueHint: "", - description: "Model ID (default: qwen-image-3.0)", + description: { + "en-US": "Model ID (default: qwen-image-3.0)", + "zh-CN": "模型 ID(默认:qwen-image-3.0)", + }, }, size: { type: "string", valueHint: "", - description: "Output image size: ratio (3:4, 16:9) or pixels (2048*2048)", + description: { + "en-US": "Output image size: ratio (3:4, 16:9) or pixels (2048*2048)", + "zh-CN": "输出图片尺寸:比例(3:4、16:9)或像素(2048*2048)", + }, }, n: { type: "number", valueHint: "", - description: "Number of images (default: 1, max: 6)", + description: { + "en-US": "Number of images (default: 1, max: 6)", + "zh-CN": "图片数量(默认:1,最多:6)", + }, + }, + seed: { + type: "number", + valueHint: "", + description: { + "en-US": "Random seed for reproducible results", + "zh-CN": "用于复现结果的随机种子", + }, }, - seed: { type: "number", valueHint: "", description: "Random seed for reproducible results" }, negativePrompt: { type: "string", valueHint: "", - description: "Negative prompt to exclude unwanted content", + description: { + "en-US": "Negative prompt to exclude unwanted content", + "zh-CN": "负向提示词,用于排除不需要的内容", + }, }, function: { type: "string", valueHint: "", - description: - "wanx*-imageedit function (default: description_edit). Examples: stylization_all, description_edit", + description: { + "en-US": + "wanx*-imageedit function (default: description_edit). Examples: stylization_all, description_edit", + "zh-CN": + "wanx*-imageedit 功能(默认:description_edit)。例如:stylization_all、description_edit", + }, }, promptExtend: { type: "boolean", @@ -81,36 +107,79 @@ const EDIT_FLAGS = { valueHint: "", description: BOOL_FLAG_WATERMARK, }, - outDir: { type: "string", valueHint: "", description: "Download images to directory" }, + outDir: { + type: "string", + valueHint: "", + description: { "en-US": "Download images to directory", "zh-CN": "将图片下载到指定目录" }, + }, outPrefix: { type: "string", valueHint: "", - description: "Filename prefix (default: edited)", + description: { + "en-US": "Filename prefix (default: edited)", + "zh-CN": "文件名前缀(默认:edited)", + }, }, ...ASYNC_FLAG, ...CONCURRENT_FLAG, pollInterval: { type: "number", valueHint: "", - description: "Polling interval when waiting (default: 3)", + description: { + "en-US": "Polling interval when waiting (default: 3)", + "zh-CN": "等待任务时的轮询间隔(默认:3 秒)", + }, }, } satisfies FlagsDef; type EditFlags = ParsedFlags; export default defineCommand({ - description: "Edit an existing image with text instructions (Qwen-Image / Wan 2.7)", + description: { + "en-US": "Edit an existing image with text instructions (Qwen-Image / Wan 2.7)", + "zh-CN": "使用文本指令编辑现有图片(Qwen-Image / Wan 2.7)", + }, auth: "apiKey", usageArgs: "--image --prompt [flags]", flags: EDIT_FLAGS, exampleArgs: [ - '--image ./photo.png --prompt "Replace the background with a beach"', - '--image https://example.com/logo.png --prompt "Change color to blue" --n 3', - '--image ./a.png --image ./b.png --prompt "Merge two images into one collage"', - '--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro', - '--image ./photo.png --prompt "Change the style" --model wan2.7-image', - '--image ./photo.png --prompt "Place the subject on a table" --model wan2.5-i2i-preview', - '--image ./photo.png --prompt "转换成绘本风格" --model wanx2.1-imageedit --function stylization_all', - '--image ./photo.png --prompt "Replace the background with a beach" --watermark false', + { + "en-US": '--image ./photo.png --prompt "Replace the background with a beach"', + "zh-CN": '--image ./photo.png --prompt "将背景替换为海滩"', + }, + { + "en-US": '--image https://example.com/logo.png --prompt "Change color to blue" --n 3', + "zh-CN": '--image https://example.com/logo.png --prompt "将颜色改为蓝色" --n 3', + }, + { + "en-US": '--image ./a.png --image ./b.png --prompt "Merge two images into one collage"', + "zh-CN": '--image ./a.png --image ./b.png --prompt "将两张图片合并成一张拼贴图"', + }, + { + "en-US": + '--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro', + "zh-CN": + '--image https://example.com/photo.png --prompt "移除人物" --model qwen-image-2.0-pro', + }, + { + "en-US": '--image ./photo.png --prompt "Change the style" --model wan2.7-image', + "zh-CN": '--image ./photo.png --prompt "更改图片风格" --model wan2.7-image', + }, + { + "en-US": + '--image ./photo.png --prompt "Place the subject on a table" --model wan2.5-i2i-preview', + "zh-CN": '--image ./photo.png --prompt "将主体放在桌面上" --model wan2.5-i2i-preview', + }, + { + "en-US": + '--image ./photo.png --prompt "Convert to a picture-book style" --model wanx2.1-imageedit --function stylization_all', + "zh-CN": + '--image ./photo.png --prompt "转换成绘本风格" --model wanx2.1-imageedit --function stylization_all', + }, + { + "en-US": + '--image ./photo.png --prompt "Replace the background with a beach" --watermark false', + "zh-CN": '--image ./photo.png --prompt "将背景替换为海滩" --watermark false', + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/image/generate.ts b/packages/commands/src/commands/image/generate.ts index 03639281..44c5d480 100644 --- a/packages/commands/src/commands/image/generate.ts +++ b/packages/commands/src/commands/image/generate.ts @@ -31,31 +31,51 @@ import { BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE, BOOL_FLAG_WATERMARK } from "bai import { join } from "path"; const GENERATE_FLAGS = { - prompt: { type: "string", valueHint: "", description: "Image description", required: true }, + prompt: { + type: "string", + valueHint: "", + description: { "en-US": "Image description", "zh-CN": "图片描述" }, + required: true, + }, model: { type: "string", valueHint: "", - description: "Model ID (default: qwen-image-3.0)", + description: { + "en-US": "Model ID (default: qwen-image-3.0)", + "zh-CN": "模型 ID(默认:qwen-image-3.0)", + }, }, size: { type: "string", valueHint: "", - description: "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)", + description: { + "en-US": "Image size: ratio (3:4, 16:9, 1:1) or pixels (2048*2048)", + "zh-CN": "图片尺寸:比例(3:4、16:9、1:1)或像素(2048*2048)", + }, }, n: { type: "number", valueHint: "", - description: "Number of images per request (default: 1, max: 6)", + description: { + "en-US": "Number of images per request (default: 1, max: 6)", + "zh-CN": "每次请求生成的图片数量(默认:1,最多:6)", + }, }, seed: { type: "number", valueHint: "", - description: "Random seed for reproducible generation", + description: { + "en-US": "Random seed for reproducible generation", + "zh-CN": "用于复现生成结果的随机种子", + }, }, negativePrompt: { type: "string", valueHint: "", - description: "Negative prompt to exclude unwanted content", + description: { + "en-US": "Negative prompt to exclude unwanted content", + "zh-CN": "负向提示词,用于排除不需要的内容", + }, }, promptExtend: { type: "boolean", @@ -69,37 +89,83 @@ const GENERATE_FLAGS = { }, ...ASYNC_FLAG, ...CONCURRENT_FLAG, - outDir: { type: "string", valueHint: "", description: "Download images to directory" }, + outDir: { + type: "string", + valueHint: "", + description: { "en-US": "Download images to directory", "zh-CN": "将图片下载到指定目录" }, + }, outPrefix: { type: "string", valueHint: "", - description: "Filename prefix (default: image)", + description: { + "en-US": "Filename prefix (default: image)", + "zh-CN": "文件名前缀(默认:image)", + }, }, pollInterval: { type: "number", valueHint: "", - description: "Polling interval when waiting (default: 3)", + description: { + "en-US": "Polling interval when waiting (default: 3)", + "zh-CN": "等待任务时的轮询间隔(默认:3 秒)", + }, }, } satisfies FlagsDef; type GenerateFlags = ParsedFlags; export default defineCommand({ - description: "Generate images (Qwen-Image / wan2.x)", + description: { + "en-US": "Generate images (Qwen-Image / wan2.x)", + "zh-CN": "生成图片(Qwen-Image / wan2.x)", + }, auth: "apiKey", usageArgs: "--prompt [flags]", flags: GENERATE_FLAGS, exampleArgs: [ - '--prompt "A cat in a spacesuit on Mars"', - '--prompt "Logo design" --n 3 --out-dir ./generated/', - '--prompt "Mountain landscape" --size 2688*1536', - '--prompt "A castle" --seed 42 --prompt-extend false', - '--prompt "Logo" --watermark false', - '--prompt "An alien in the space" --watermark false', - '--prompt "sunset" --model wan2.6-t2i --async --quiet', - '--prompt "plush doll" --model z-image-turbo --size 1024*1024', - '--prompt "sunset" --model wanx2.0-t2i-turbo --size 1024*1024', - '--prompt "Pro quality" --model qwen-image-2.0-pro', - '--prompt "Product shots" --n 2 --concurrent 3 # 6 images in parallel', + { + "en-US": '--prompt "A cat in a spacesuit on Mars"', + "zh-CN": '--prompt "一只穿着宇航服的猫站在火星上"', + }, + { + "en-US": '--prompt "Logo design" --n 3 --out-dir ./generated/', + "zh-CN": '--prompt "Logo 设计" --n 3 --out-dir ./generated/', + }, + { + "en-US": '--prompt "Mountain landscape" --size 2688*1536', + "zh-CN": '--prompt "山地景观" --size 2688*1536', + }, + { + "en-US": '--prompt "A castle" --seed 42 --prompt-extend false', + "zh-CN": '--prompt "一座城堡" --seed 42 --prompt-extend false', + }, + { + "en-US": '--prompt "Logo" --watermark false', + "zh-CN": '--prompt "Logo" --watermark false', + }, + { + "en-US": '--prompt "An alien in the space" --watermark false', + "zh-CN": '--prompt "太空中的外星人" --watermark false', + }, + { + "en-US": '--prompt "sunset" --model wan2.6-t2i --async --quiet', + "zh-CN": '--prompt "日落" --model wan2.6-t2i --async --quiet', + }, + { + "en-US": '--prompt "plush doll" --model z-image-turbo --size 1024*1024', + "zh-CN": '--prompt "毛绒玩偶" --model z-image-turbo --size 1024*1024', + }, + { + "en-US": '--prompt "sunset" --model wanx2.0-t2i-turbo --size 1024*1024', + "zh-CN": '--prompt "日落" --model wanx2.0-t2i-turbo --size 1024*1024', + }, + { + "en-US": '--prompt "Pro quality" --model qwen-image-2.0-pro', + "zh-CN": '--prompt "专业品质" --model qwen-image-2.0-pro', + }, + { + "en-US": '--prompt "Product shots" --n 2 --concurrent 3 # 6 images in parallel', + "zh-CN": '--prompt "产品摄影" --n 2 --concurrent 3 # 并行生成 6 张图片', + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/knowledge/category-add.ts b/packages/commands/src/commands/knowledge/category-add.ts index 57c25dcc..c7bc6041 100644 --- a/packages/commands/src/commands/knowledge/category-add.ts +++ b/packages/commands/src/commands/knowledge/category-add.ts @@ -13,28 +13,39 @@ const CATEGORY_ADD_FLAGS = { name: { type: "string", valueHint: "", - description: "Category name (1-20 chars)", + description: { "en-US": "Category name (1-20 chars)", "zh-CN": "类目名称(1–20 个字符)" }, required: true, }, parentId: { type: "string", valueHint: "", - description: "Create as a sub-category of this category", + description: { + "en-US": "Create as a sub-category of this category", + "zh-CN": "创建为该类目的子类目", + }, }, collectionId: { type: "string", valueHint: "", - description: "Create under this collection (defaults to the platform collection)", + description: { + "en-US": "Create under this collection (defaults to the platform collection)", + "zh-CN": "在该数据集合下创建(默认为平台数据集合)", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Create a data-center category", + description: { "en-US": "Create a data-center category", "zh-CN": "创建数据中心类目" }, auth: "apiKey", usageArgs: "--name [flags]", flags: CATEGORY_ADD_FLAGS, - notes: ["Use categories to organize data-center files by business domain."], + notes: [ + { + "en-US": "Use categories to organize data-center files by business domain.", + "zh-CN": "使用类目按业务领域组织数据中心文件。", + }, + ], exampleArgs: ["--name product-docs --workspace-id ws-xxx", "--name sub --parent-id cate-xxx"], validate(flags) { if (flags.name.length < 1 || flags.name.length > 20) return "--name must be 1-20 characters"; diff --git a/packages/commands/src/commands/knowledge/category-delete.ts b/packages/commands/src/commands/knowledge/category-delete.ts index 58c0c28b..3c94e9f9 100644 --- a/packages/commands/src/commands/knowledge/category-delete.ts +++ b/packages/commands/src/commands/knowledge/category-delete.ts @@ -13,20 +13,27 @@ const CATEGORY_DELETE_FLAGS = { categoryId: { type: "string", valueHint: "", - description: "Category ID to delete", + description: { "en-US": "Category ID to delete", "zh-CN": "要删除的类目 ID" }, required: true, }, - yes: { type: "switch", description: "Skip the confirmation prompt" }, + yes: { + type: "switch", + description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" }, + }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Delete a data-center category", + description: { "en-US": "Delete a data-center category", "zh-CN": "删除数据中心类目" }, auth: "apiKey", usageArgs: "--category-id [flags]", flags: CATEGORY_DELETE_FLAGS, notes: [ - "Behavior for categories containing files or sub-categories is server-defined — the server error is passed through as-is.", + { + "en-US": + "Behavior for categories containing files or sub-categories is server-defined — the server error is passed through as-is.", + "zh-CN": "包含文件或子类目时的处理行为由服务端定义——服务端返回的错误将原样透传。", + }, ], exampleArgs: ["--category-id cate-xxx --workspace-id ws-xxx", "--category-id cate-xxx --yes"], async run(ctx) { diff --git a/packages/commands/src/commands/knowledge/category-list.ts b/packages/commands/src/commands/knowledge/category-list.ts index b1480bea..a4ba34ca 100644 --- a/packages/commands/src/commands/knowledge/category-list.ts +++ b/packages/commands/src/commands/knowledge/category-list.ts @@ -13,39 +13,54 @@ const CATEGORY_LIST_FLAGS = { collectionId: { type: "string", valueHint: "", - description: "Filter by exact collection ID", + description: { "en-US": "Filter by exact collection ID", "zh-CN": "按数据集合 ID 精确筛选" }, }, parentId: { type: "string", valueHint: "", - description: "List sub-categories of this exact parent category", + description: { + "en-US": "List sub-categories of this exact parent category", + "zh-CN": "列出该父类目下的子类目", + }, }, name: { type: "string", valueHint: "", - description: "Filter by category name (exact match, unlike the knowledge base list)", + description: { + "en-US": "Filter by category name (exact match, unlike the knowledge base list)", + "zh-CN": "按类目名称筛选(精确匹配,与知识库列表不同)", + }, }, nextToken: { type: "string", valueHint: "", - description: "Cursor for the next page (from previous output)", + description: { + "en-US": "Cursor for the next page (from previous output)", + "zh-CN": "下一页游标(来自上一次输出)", + }, }, maxResult: { type: "number", valueHint: "", - description: "Items per page (default: 20)", + description: { "en-US": "Items per page (default: 20)", "zh-CN": "每页条目数(默认:20)" }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "List data-center categories", + description: { "en-US": "List data-center categories", "zh-CN": "列出数据中心类目" }, auth: "apiKey", usageArgs: "[flags]", flags: CATEGORY_LIST_FLAGS, notes: [ - "Categories marked [default] are where files land when no category is specified.", - "Pagination is cursor-based: reuse the printed next token to continue.", + { + "en-US": "Categories marked [default] are where files land when no category is specified.", + "zh-CN": "未指定类目时,文件会进入标记为 [default] 的类目。", + }, + { + "en-US": "Pagination is cursor-based: reuse the printed next token to continue.", + "zh-CN": "分页使用游标:复用输出中的 next token 继续查询。", + }, ], exampleArgs: ["--workspace-id ws-xxx", "--name my-category", "--next-token "], async run(ctx) { diff --git a/packages/commands/src/commands/knowledge/chat.ts b/packages/commands/src/commands/knowledge/chat.ts index 3e5e03c1..aaa7b1fa 100644 --- a/packages/commands/src/commands/knowledge/chat.ts +++ b/packages/commands/src/commands/knowledge/chat.ts @@ -19,13 +19,20 @@ const CHAT_FLAGS = { message: { type: "array", valueHint: "", - description: - "Message text (repeatable). Supports role:content prefix to set role (e.g. user:hello), defaults to user. Follows OpenAI message format", + description: { + "en-US": + "Message text (repeatable). Supports role:content prefix to set role (e.g. user:hello), defaults to user. Follows OpenAI message format", + "zh-CN": + "消息文本(可重复)。支持使用 role:content 前缀指定角色(例如 user:hello),默认为 user。遵循 OpenAI 消息格式", + }, }, agentId: { type: "string", valueHint: "", - description: "Q&A service ID (find in console knowledge Q&A page)", + description: { + "en-US": "Q&A service ID (find in console knowledge Q&A page)", + "zh-CN": "问答服务 ID(可在控制台知识库问答页面查看)", + }, required: true, }, // Knowledge APIs use a workspace-specific host, so --workspace-id is a per-command @@ -35,13 +42,19 @@ const CHAT_FLAGS = { agentVersion: { type: "string", valueHint: "", - description: - "Service version to call: beta (draft for debugging) or a published number; default is the latest published version", + description: { + "en-US": + "Service version to call: beta (draft for debugging) or a published number; default is the latest published version", + "zh-CN": "要调用的服务版本:beta(用于调试的草稿)或已发布版本号;默认使用最新发布版本", + }, }, image: { type: "array", valueHint: "", - description: "Image URL (repeatable). Attached to the last user message as multimodal content", + description: { + "en-US": "Image URL (repeatable). Attached to the last user message as multimodal content", + "zh-CN": "图片 URL(可重复)。将作为多模态内容附加到最后一条用户消息", + }, }, } satisfies FlagsDef; type ChatFlags = ParsedFlags; @@ -142,21 +155,58 @@ const STEP_LABELS: Record = { }; export default defineCommand({ - description: "Chat with a Bailian knowledge base (RAG Q&A with streaming)", + description: { + "en-US": "Chat with a Bailian knowledge base (RAG Q&A with streaming)", + "zh-CN": "与百炼知识库对话(支持流式输出的 RAG 问答)", + }, auth: "apiKey", usageArgs: "--message --agent-id [flags]", flags: CHAT_FLAGS, notes: [ - "Response is returned as SSE stream events. Event lifecycle: tool_calling → tool_return → plan_start → planning → plan_end → generation_start → generating → generation_end. tool_calling → tool_return may loop multiple times.", - "Auth: uses DashScope API Key (Bearer token). Get yours from the console API Key page.", - "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or `kscli config set workspace_id `.", - 'Multi-turn: use --message "user:..." and --message "assistant:..." to pass conversation history.', - "`--agent-version beta` calls the draft config for debugging before it is deployed.", + { + "en-US": + "Response is returned as SSE stream events. Event lifecycle: tool_calling → tool_return → plan_start → planning → plan_end → generation_start → generating → generation_end. tool_calling → tool_return may loop multiple times.", + "zh-CN": + "响应以 SSE 流事件返回。事件生命周期:tool_calling → tool_return → plan_start → planning → plan_end → generation_start → generating → generation_end。tool_calling → tool_return 可能循环多次。", + }, + { + "en-US": + "Auth: uses DashScope API Key (Bearer token). Get yours from the console API Key page.", + "zh-CN": "鉴权:使用 DashScope API Key(Bearer Token)。可在控制台 API Key 页面获取。", + }, + { + "en-US": + "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or `kscli config set workspace_id `.", + "zh-CN": + "`--workspace-id` 可通过 BAILIAN_WORKSPACE_ID 环境变量或 `kscli config set workspace_id ` 设置。", + }, + { + "en-US": + 'Multi-turn: use --message "user:..." and --message "assistant:..." to pass conversation history.', + "zh-CN": '多轮对话:使用 --message "user:..." 和 --message "assistant:..." 传入对话历史。', + }, + { + "en-US": "`--agent-version beta` calls the draft config for debugging before it is deployed.", + "zh-CN": "`--agent-version beta` 会调用尚未部署的草稿配置,便于发布前调试。", + }, ], exampleArgs: [ - '--message "What is RAG?" --agent-id aid-xxx --workspace-id ws-xxx', - '--message "user:What is RAG?" --message "assistant:RAG is..." --message "How does it work?" --agent-id aid-xxx --workspace-id ws-xxx', - '--message "Describe these images" --image https://example.com/a.png --image https://example.com/b.png --agent-id aid-xxx --workspace-id ws-xxx', + { + "en-US": '--message "What is RAG?" --agent-id aid-xxx --workspace-id ws-xxx', + "zh-CN": '--message "什么是 RAG?" --agent-id aid-xxx --workspace-id ws-xxx', + }, + { + "en-US": + '--message "user:What is RAG?" --message "assistant:RAG is..." --message "How does it work?" --agent-id aid-xxx --workspace-id ws-xxx', + "zh-CN": + '--message "user:什么是 RAG?" --message "assistant:RAG 是……" --message "它是如何工作的?" --agent-id aid-xxx --workspace-id ws-xxx', + }, + { + "en-US": + '--message "Describe these images" --image https://example.com/a.png --image https://example.com/b.png --agent-id aid-xxx --workspace-id ws-xxx', + "zh-CN": + '--message "描述这些图片" --image https://example.com/a.png --image https://example.com/b.png --agent-id aid-xxx --workspace-id ws-xxx', + }, ], validate: (f) => (f.message && f.message.length > 0) || (f.image && f.image.length > 0) diff --git a/packages/commands/src/commands/knowledge/chunk-add.ts b/packages/commands/src/commands/knowledge/chunk-add.ts index de4eb505..890511c5 100644 --- a/packages/commands/src/commands/knowledge/chunk-add.ts +++ b/packages/commands/src/commands/knowledge/chunk-add.ts @@ -16,40 +16,59 @@ const CHUNK_ADD_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, docId: { type: "string", valueHint: "", - description: - "Owning document ID from the doc list command; required in practice for all knowledge base types", + description: { + "en-US": + "Owning document ID from the doc list command; required in practice for all knowledge base types", + "zh-CN": "来自文档列表命令的所属文档 ID;实际使用时所有知识库类型都需要提供", + }, }, content: { type: "string", valueHint: "", - description: "Chunk body text, up to 6000 chars (document-type); alternative to --content-file", + description: { + "en-US": "Chunk body text, up to 6000 chars (document-type); alternative to --content-file", + "zh-CN": "Chunk 正文,最多 6000 个字符(文档型);与 --content-file 二选一", + }, }, contentFile: { type: "string", valueHint: "", - description: "Read chunk body from a UTF-8 plain text file (.md/.txt etc.)", + description: { + "en-US": "Read chunk body from a UTF-8 plain text file (.md/.txt etc.)", + "zh-CN": "从 UTF-8 纯文本文件(.md/.txt 等)读取 Chunk 正文", + }, }, title: { type: "string", valueHint: "", - description: "Chunk title, up to 50 chars (document-type)", + description: { + "en-US": "Chunk title, up to 50 chars (document-type)", + "zh-CN": "Chunk 标题,最多 50 个字符(文档型)", + }, }, imageUrl: { type: "array", valueHint: "", - description: "Chunk image URL (repeatable, up to 10; document-type)", + description: { + "en-US": "Chunk image URL (repeatable, up to 10; document-type)", + "zh-CN": "Chunk 图片 URL(可重复,最多 10 个;文档型)", + }, }, field: { type: "array", valueHint: "", - description: - "Arbitrary field entry (repeatable) for table/image knowledge bases where keys are Excel column headers; mutually exclusive with content/title/image flags", + description: { + "en-US": + "Arbitrary field entry (repeatable) for table/image knowledge bases where keys are Excel column headers; mutually exclusive with content/title/image flags", + "zh-CN": + "表格/图片知识库的自定义字段(可重复),键为 Excel 列标题;不能与 content/title/image 相关选项同时使用", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -68,21 +87,53 @@ export function parseFieldEntries(entries: string[]): Record { } export default defineCommand({ - description: "Add a chunk directly to a knowledge base", + description: { + "en-US": "Add a chunk directly to a knowledge base", + "zh-CN": "直接向知识库添加 Chunk", + }, auth: "apiKey", usageArgs: "--index-id (--content | --field ) [flags]", flags: CHUNK_ADD_FLAGS, notes: [ - "Document / table / image knowledge bases are supported; audio-video ones are not.", - "--doc-id is required in practice for all knowledge base types. Use the document-level id from the doc list command; the per-row doc_id in chunk list output is not accepted.", - "Image-type documents do not support text chunks. Target a text-type document (docx/pdf/txt) instead.", - "The API is idempotent but rate-limited to 10 calls per second — throttle batch scripts.", - "The response carries no chunk id; list chunks afterwards to find the new one.", - "For table/image knowledge bases use --field with Excel column headers as keys; values are passed through as strings.", + { + "en-US": "Document / table / image knowledge bases are supported; audio-video ones are not.", + "zh-CN": "支持文档、表格和图片知识库;不支持音视频知识库。", + }, + { + "en-US": + "--doc-id is required in practice for all knowledge base types. Use the document-level id from the doc list command; the per-row doc_id in chunk list output is not accepted.", + "zh-CN": + "实际使用时,所有知识库类型都需要 --doc-id。请使用文档列表命令返回的文档级 ID;Chunk 列表每行返回的 doc_id 不被接受。", + }, + { + "en-US": + "Image-type documents do not support text chunks. Target a text-type document (docx/pdf/txt) instead.", + "zh-CN": "图片型文档不支持文本 Chunk;请改为操作文本型文档(docx/pdf/txt)。", + }, + { + "en-US": + "The API is idempotent but rate-limited to 10 calls per second — throttle batch scripts.", + "zh-CN": "该 API 具有幂等性,但限流为每秒 10 次调用——批处理脚本需要控制速率。", + }, + { + "en-US": "The response carries no chunk id; list chunks afterwards to find the new one.", + "zh-CN": "响应不包含 Chunk ID;添加后请列出 Chunk 以查找新条目。", + }, + { + "en-US": + "For table/image knowledge bases use --field with Excel column headers as keys; values are passed through as strings.", + "zh-CN": "表格/图片知识库请使用 --field,并以 Excel 列标题作为键;值将按字符串原样传递。", + }, ], exampleArgs: [ - '--index-id idx-xxx --content "chunk text" --title intro --workspace-id ws-xxx', - "--index-id idx-xxx --field 列A=v1 --field 列B=v2", + { + "en-US": '--index-id idx-xxx --content "chunk text" --title intro --workspace-id ws-xxx', + "zh-CN": '--index-id idx-xxx --content "Chunk 文本" --title 简介 --workspace-id ws-xxx', + }, + { + "en-US": "--index-id idx-xxx --field columnA=v1 --field columnB=v2", + "zh-CN": "--index-id idx-xxx --field 列A=v1 --field 列B=v2", + }, ], validate(flags) { const hasConvenience = diff --git a/packages/commands/src/commands/knowledge/chunk-delete.ts b/packages/commands/src/commands/knowledge/chunk-delete.ts index 36f70aea..e8959362 100644 --- a/packages/commands/src/commands/knowledge/chunk-delete.ts +++ b/packages/commands/src/commands/knowledge/chunk-delete.ts @@ -14,16 +14,22 @@ const CHUNK_DELETE_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, chunkId: { type: "array", valueHint: "", - description: "Chunk ID to delete (repeatable; batches of 10 are sent automatically)", + description: { + "en-US": "Chunk ID to delete (repeatable; batches of 10 are sent automatically)", + "zh-CN": "要删除的 Chunk ID(可重复;每 10 个自动分批发送)", + }, required: true, }, - yes: { type: "switch", description: "Skip the confirmation prompt" }, + yes: { + type: "switch", + description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" }, + }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -37,11 +43,19 @@ export function splitIntoBatches(chunkIds: string[], batchSize = 10): string[][] } export default defineCommand({ - description: "Delete chunks from a knowledge base (irreversible)", + description: { + "en-US": "Delete chunks from a knowledge base (irreversible)", + "zh-CN": "从知识库中删除 Chunk(不可撤销)", + }, auth: "apiKey", usageArgs: "--index-id --chunk-id [flags]", flags: CHUNK_DELETE_FLAGS, - notes: ["Accepts at most 10 chunk ids per call; larger sets are batched automatically."], + notes: [ + { + "en-US": "Accepts at most 10 chunk ids per call; larger sets are batched automatically.", + "zh-CN": "每次调用最多接受 10 个 Chunk ID;更多 ID 会自动分批处理。", + }, + ], exampleArgs: [ "--index-id idx-xxx --chunk-id chunk-a --chunk-id chunk-b --workspace-id ws-xxx", "--index-id idx-xxx --chunk-id chunk-a --yes", diff --git a/packages/commands/src/commands/knowledge/chunk-list.ts b/packages/commands/src/commands/knowledge/chunk-list.ts index 13f949de..b306219d 100644 --- a/packages/commands/src/commands/knowledge/chunk-list.ts +++ b/packages/commands/src/commands/knowledge/chunk-list.ts @@ -13,26 +13,40 @@ const CHUNK_LIST_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, docId: { type: "string", valueHint: "", - description: "Only show chunks belonging to this document", + description: { + "en-US": "Only show chunks belonging to this document", + "zh-CN": "仅显示属于该文档的 Chunk", + }, }, ...PAGE_FLAGS, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "List chunks in a knowledge base with content and status", + description: { + "en-US": "List chunks in a knowledge base with content and status", + "zh-CN": "列出知识库中的 Chunk 及其内容和状态", + }, auth: "apiKey", usageArgs: "--index-id [flags]", flags: CHUNK_LIST_FLAGS, notes: [ - "Use metadata._id as the chunk id and metadata.doc_id as the document id in chunk update/delete commands.", - "Page size defaults to 20 (server default), max 100.", + { + "en-US": + "Use metadata._id as the chunk id and metadata.doc_id as the document id in chunk update/delete commands.", + "zh-CN": + "在 Chunk 更新/删除命令中,使用 metadata._id 作为 Chunk ID,使用 metadata.doc_id 作为文档 ID。", + }, + { + "en-US": "Page size defaults to 20 (server default), max 100.", + "zh-CN": "分页大小默认为 20(服务端默认值),最大为 100。", + }, ], exampleArgs: [ "--index-id idx-xxx --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/chunk-update.ts b/packages/commands/src/commands/knowledge/chunk-update.ts index c14e87ca..3fc53c6d 100644 --- a/packages/commands/src/commands/knowledge/chunk-update.ts +++ b/packages/commands/src/commands/knowledge/chunk-update.ts @@ -18,38 +18,65 @@ const CHUNK_UPDATE_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, chunkId: { type: "string", valueHint: "", - description: "Chunk ID (metadata._id from the chunk list output)", + description: { + "en-US": "Chunk ID (metadata._id from the chunk list output)", + "zh-CN": "Chunk ID(来自 Chunk 列表输出的 metadata._id)", + }, required: true, }, docId: { type: "string", valueHint: "", - description: "Document ID owning the chunk (metadata.doc_id from the chunk list output)", + description: { + "en-US": "Document ID owning the chunk (metadata.doc_id from the chunk list output)", + "zh-CN": "Chunk 所属文档 ID(来自 Chunk 列表输出的 metadata.doc_id)", + }, required: true, }, content: { type: "string", valueHint: "", - description: "New chunk content, 10-6000 chars; alternative to --content-file", + description: { + "en-US": "New chunk content, 10-6000 chars; alternative to --content-file", + "zh-CN": "新的 Chunk 内容,10–6000 个字符;与 --content-file 二选一", + }, }, contentFile: { type: "string", valueHint: "", - description: "Read new content from a UTF-8 plain text file (.md/.txt etc.)", + description: { + "en-US": "Read new content from a UTF-8 plain text file (.md/.txt etc.)", + "zh-CN": "从 UTF-8 纯文本文件(.md/.txt 等)读取新内容", + }, }, title: { type: "string", valueHint: "", - description: "Chunk title, 0-50 chars (empty string clears it; omit to keep unchanged)", + description: { + "en-US": "Chunk title, 0-50 chars (empty string clears it; omit to keep unchanged)", + "zh-CN": "Chunk 标题,0–50 个字符(空字符串表示清除;省略则保持不变)", + }, + }, + exclude: { + type: "switch", + description: { + "en-US": "Exclude this chunk from retrieval", + "zh-CN": "在检索中排除该 Chunk", + }, + }, + include: { + type: "switch", + description: { + "en-US": "Include this chunk in retrieval (default)", + "zh-CN": "在检索中包含该 Chunk(默认)", + }, }, - exclude: { type: "switch", description: "Exclude this chunk from retrieval" }, - include: { type: "switch", description: "Include this chunk in retrieval (default)" }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -82,17 +109,35 @@ async function fetchChunkContent( } export default defineCommand({ - description: "Update chunk content or toggle its retrieval visibility", + description: { + "en-US": "Update chunk content or toggle its retrieval visibility", + "zh-CN": "更新 Chunk 内容或切换其检索可见性", + }, auth: "apiKey", usageArgs: "--index-id --chunk-id --doc-id [flags]", flags: CHUNK_UPDATE_FLAGS, notes: [ - "Content must be 10-6000 characters and within the knowledge base's max chunk size.", - "--content-file expects a UTF-8 plain text file; document formats (.docx/.pdf) are not parsed here.", - "Toggling --exclude/--include without new content re-submits the existing content automatically.", + { + "en-US": "Content must be 10-6000 characters and within the knowledge base's max chunk size.", + "zh-CN": "内容必须为 10–6000 个字符,且不能超过知识库的最大 Chunk 大小。", + }, + { + "en-US": + "--content-file expects a UTF-8 plain text file; document formats (.docx/.pdf) are not parsed here.", + "zh-CN": "--content-file 需要 UTF-8 纯文本文件;此处不会解析 .docx/.pdf 等文档格式。", + }, + { + "en-US": + "Toggling --exclude/--include without new content re-submits the existing content automatically.", + "zh-CN": "仅切换 --exclude/--include 而不提供新内容时,会自动重新提交现有内容。", + }, ], exampleArgs: [ - '--index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --content "corrected text"', + { + "en-US": + '--index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --content "corrected text"', + "zh-CN": '--index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --content "修正后的文本"', + }, "--index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --exclude", ], validate(flags) { diff --git a/packages/commands/src/commands/knowledge/collection-create.ts b/packages/commands/src/commands/knowledge/collection-create.ts index 67fa53f3..2db8031e 100644 --- a/packages/commands/src/commands/knowledge/collection-create.ts +++ b/packages/commands/src/commands/knowledge/collection-create.ts @@ -13,46 +13,78 @@ const COLLECTION_CREATE_FLAGS = { name: { type: "string", valueHint: "", - description: "Collection name", + description: { "en-US": "Collection name", "zh-CN": "数据集合名称" }, required: true, }, description: { type: "string", valueHint: "", - description: "Collection description (required by the server)", + description: { + "en-US": "Collection description (required by the server)", + "zh-CN": "数据集合描述(服务端必填)", + }, required: true, }, storeType: { type: "string", valueHint: "", - description: "Storage: platform (managed) or custom (your own OSS bucket)", + description: { + "en-US": "Storage: platform (managed) or custom (your own OSS bucket)", + "zh-CN": "存储类型:platform(托管)或 custom(自有 OSS Bucket)", + }, }, ossRegion: { type: "string", valueHint: "", - description: "OSS region id (required with --store-type custom)", + description: { + "en-US": "OSS region id (required with --store-type custom)", + "zh-CN": "OSS Region ID(使用 --store-type custom 时必填)", + }, }, ossBucket: { type: "string", valueHint: "", - description: "OSS bucket name (required with --store-type custom)", + description: { + "en-US": "OSS bucket name (required with --store-type custom)", + "zh-CN": "OSS Bucket 名称(使用 --store-type custom 时必填)", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Create a FILE data collection", + description: { "en-US": "Create a FILE data collection", "zh-CN": "创建 FILE 数据集合" }, auth: "apiKey", usageArgs: "--name --description [flags]", flags: COLLECTION_CREATE_FLAGS, notes: [ - "Store type defaults to platform (managed storage); custom uses your authorized OSS bucket.", - "Custom buckets must carry the bucket tag bailian-connector-access=ReadAndWrite (Bailian's tag-based access control); without it the server rejects creation with a misleading 'setBucketCORS failed' error.", - "There is no collection delete API — create collections deliberately.", + { + "en-US": + "Store type defaults to platform (managed storage); custom uses your authorized OSS bucket.", + "zh-CN": "存储类型默认为 platform(托管存储);custom 使用已授权的自有 OSS Bucket。", + }, + { + "en-US": + "Custom buckets must carry the bucket tag bailian-connector-access=ReadAndWrite (Bailian's tag-based access control); without it the server rejects creation with a misleading 'setBucketCORS failed' error.", + "zh-CN": + "自定义 Bucket 必须带有 bailian-connector-access=ReadAndWrite 标签(百炼基于标签的访问控制);缺少该标签时,服务端会以容易误解的 'setBucketCORS failed' 错误拒绝创建。", + }, + { + "en-US": "There is no collection delete API — create collections deliberately.", + "zh-CN": "目前没有删除数据集合的 API——请谨慎创建。", + }, ], exampleArgs: [ - "--name my-collection --description 'team docs' --workspace-id ws-xxx", - "--name oss-coll --description 'own bucket' --store-type custom --oss-region cn-beijing --oss-bucket my-bucket", + { + "en-US": "--name my-collection --description 'team docs' --workspace-id ws-xxx", + "zh-CN": "--name my-collection --description '团队文档' --workspace-id ws-xxx", + }, + { + "en-US": + "--name oss-coll --description 'own bucket' --store-type custom --oss-region cn-beijing --oss-bucket my-bucket", + "zh-CN": + "--name oss-coll --description '自有 Bucket' --store-type custom --oss-region cn-beijing --oss-bucket my-bucket", + }, ], validate(flags) { // Server rejects names longer than 20 characters ("Connector name is longer than 20") diff --git a/packages/commands/src/commands/knowledge/collection-get.ts b/packages/commands/src/commands/knowledge/collection-get.ts index 62be71f3..927b6c67 100644 --- a/packages/commands/src/commands/knowledge/collection-get.ts +++ b/packages/commands/src/commands/knowledge/collection-get.ts @@ -13,18 +13,24 @@ const COLLECTION_GET_FLAGS = { collectionId: { type: "string", valueHint: "", - description: "Collection ID; alternative to --name", + description: { + "en-US": "Collection ID; alternative to --name", + "zh-CN": "数据集合 ID;与 --name 二选一", + }, }, name: { type: "string", valueHint: "", - description: "Collection name; alternative to --collection-id", + description: { + "en-US": "Collection name; alternative to --collection-id", + "zh-CN": "数据集合名称;与 --collection-id 二选一", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Show data collection details", + description: { "en-US": "Show data collection details", "zh-CN": "查看数据集合详情" }, auth: "apiKey", usageArgs: "(--collection-id | --name ) [flags]", flags: COLLECTION_GET_FLAGS, diff --git a/packages/commands/src/commands/knowledge/doc-delete.ts b/packages/commands/src/commands/knowledge/doc-delete.ts index 356399ae..915b9e7c 100644 --- a/packages/commands/src/commands/knowledge/doc-delete.ts +++ b/packages/commands/src/commands/knowledge/doc-delete.ts @@ -13,16 +13,22 @@ const DOC_DELETE_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, docId: { type: "array", valueHint: "", - description: "Document ID to delete (repeatable)", + description: { + "en-US": "Document ID to delete (repeatable)", + "zh-CN": "要删除的文档 ID(可重复)", + }, required: true, }, - yes: { type: "switch", description: "Skip the confirmation prompt" }, + yes: { + type: "switch", + description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" }, + }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -36,15 +42,34 @@ function buildDeleteSummary(indexId: string, docIds: string[]): string { } export default defineCommand({ - description: "Delete documents and their chunks from a knowledge base", + description: { + "en-US": "Delete documents and their chunks from a knowledge base", + "zh-CN": "从知识库中删除文档及其 Chunk", + }, auth: "apiKey", usageArgs: "--index-id --doc-id [flags]", flags: DOC_DELETE_FLAGS, notes: [ - "Removes documents from the knowledge base index only; the source files remain in the data center.", - "Use the doc_id from `knowledge doc list --quiet`, not the fileId from `knowledge doc upload`. For documents created via `knowledge create --doc-id`, the doc_id equals the fileId; for documents imported via `knowledge doc upload --index-id`, the doc_id may include a workspace suffix.", - "Deletion may take up to ~30s to propagate — the document may still appear in the doc list briefly.", - "The output lists the ids actually deleted.", + { + "en-US": + "Removes documents from the knowledge base index only; the source files remain in the data center.", + "zh-CN": "仅从知识库索引中移除文档;源文件仍保留在数据中心。", + }, + { + "en-US": + "Use the doc_id from `knowledge doc list --quiet`, not the fileId from `knowledge doc upload`. For documents created via `knowledge create --doc-id`, the doc_id equals the fileId; for documents imported via `knowledge doc upload --index-id`, the doc_id may include a workspace suffix.", + "zh-CN": + "请使用 `knowledge doc list --quiet` 返回的 doc_id,而不是 `knowledge doc upload` 返回的 fileId。通过 `knowledge create --doc-id` 创建的文档,其 doc_id 等于 fileId;通过 `knowledge doc upload --index-id` 导入的文档,其 doc_id 可能带有 Workspace 后缀。", + }, + { + "en-US": + "Deletion may take up to ~30s to propagate — the document may still appear in the doc list briefly.", + "zh-CN": "删除结果最多可能需要约 30 秒才会生效——文档可能会短暂地继续出现在列表中。", + }, + { + "en-US": "The output lists the ids actually deleted.", + "zh-CN": "输出会列出实际删除的 ID。", + }, ], exampleArgs: [ "--index-id idx-xxx --doc-id file-xxx --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/doc-import-oss.ts b/packages/commands/src/commands/knowledge/doc-import-oss.ts index 9db0468c..65206af2 100644 --- a/packages/commands/src/commands/knowledge/doc-import-oss.ts +++ b/packages/commands/src/commands/knowledge/doc-import-oss.ts @@ -14,47 +14,78 @@ const DOC_IMPORT_OSS_FLAGS = { bucket: { type: "string", valueHint: "", - description: "Authorized OSS bucket name", + description: { "en-US": "Authorized OSS bucket name", "zh-CN": "已授权的 OSS Bucket 名称" }, required: true, }, region: { type: "string", valueHint: "", - description: "OSS region id (e.g. cn-beijing)", + description: { + "en-US": "OSS region id (e.g. cn-beijing)", + "zh-CN": "OSS Region ID(例如 cn-beijing)", + }, required: true, }, ossKey: { type: "array", valueHint: "", - description: "OSS object key to import (repeatable, 1-10 per call)", + description: { + "en-US": "OSS object key to import (repeatable, 1-10 per call)", + "zh-CN": "要导入的 OSS Object Key(可重复,每次调用 1–10 个)", + }, required: true, }, categoryId: { type: "string", valueHint: "", - description: "Target data-center category (default: the default category)", + description: { + "en-US": "Target data-center category (default: the default category)", + "zh-CN": "目标数据中心类目(默认:默认类目)", + }, }, tag: { type: "array", valueHint: "", - description: "File tag applied to every imported file (repeatable, up to 10)", + description: { + "en-US": "File tag applied to every imported file (repeatable, up to 10)", + "zh-CN": "应用于每个导入文件的标签(可重复,最多 10 个)", + }, }, overwrite: { type: "switch", - description: "Overwrite files previously imported from the same OSS keys", + description: { + "en-US": "Overwrite files previously imported from the same OSS keys", + "zh-CN": "覆盖此前从相同 OSS Key 导入的文件", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Batch import files from an authorized OSS bucket into the data center", + description: { + "en-US": "Batch import files from an authorized OSS bucket into the data center", + "zh-CN": "从已授权的 OSS Bucket 批量导入文件到数据中心", + }, auth: "apiKey", usageArgs: "--bucket --region --oss-key [flags]", flags: DOC_IMPORT_OSS_FLAGS, notes: [ - "The bucket must be authorized to the platform service role beforehand; permission errors from the server are passed through with a pointer to check AliyunServiceRoleForBailian in the RAM console.", - "File names are derived from the OSS key basename.", - "--overwrite replaces the previously imported file and issues a NEW fileId (the old one becomes invalid) — verified live.", + { + "en-US": + "The bucket must be authorized to the platform service role beforehand; permission errors from the server are passed through with a pointer to check AliyunServiceRoleForBailian in the RAM console.", + "zh-CN": + "必须预先将 Bucket 授权给平台服务角色;服务端权限错误将原样透传,并提示在 RAM 控制台检查 AliyunServiceRoleForBailian。", + }, + { + "en-US": "File names are derived from the OSS key basename.", + "zh-CN": "文件名取自 OSS Key 的 basename。", + }, + { + "en-US": + "--overwrite replaces the previously imported file and issues a NEW fileId (the old one becomes invalid) — verified live.", + "zh-CN": + "--overwrite 会替换此前导入的文件并生成新的 fileId(旧 fileId 将失效)——已通过真实环境验证。", + }, ], exampleArgs: [ "--bucket my-bucket --region cn-beijing --oss-key docs/a.pdf --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/doc-list.ts b/packages/commands/src/commands/knowledge/doc-list.ts index 8fe5398e..2e3415a3 100644 --- a/packages/commands/src/commands/knowledge/doc-list.ts +++ b/packages/commands/src/commands/knowledge/doc-list.ts @@ -13,7 +13,7 @@ const DOC_LIST_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, ...PAGE_FLAGS, @@ -21,13 +21,23 @@ const DOC_LIST_FLAGS = { } satisfies FlagsDef; export default defineCommand({ - description: "List documents in a knowledge base with parse/index status", + description: { + "en-US": "List documents in a knowledge base with parse/index status", + "zh-CN": "列出知识库文档及其解析/索引状态", + }, auth: "apiKey", usageArgs: "--index-id [flags]", flags: DOC_LIST_FLAGS, notes: [ - "Documents with status FAILED are highlighted in text mode — use the import job status command to inspect failures.", - "Page size defaults to 10 (server default), max 100.", + { + "en-US": + "Documents with status FAILED are highlighted in text mode — use the import job status command to inspect failures.", + "zh-CN": "文本模式会突出显示状态为 FAILED 的文档——请使用导入任务状态命令检查失败详情。", + }, + { + "en-US": "Page size defaults to 10 (server default), max 100.", + "zh-CN": "分页大小默认为 10(服务端默认值),最大为 100。", + }, ], exampleArgs: ["--index-id idx-xxx --workspace-id ws-xxx", "--index-id idx-xxx --page-size 100"], validate(flags) { diff --git a/packages/commands/src/commands/knowledge/doc-status.ts b/packages/commands/src/commands/knowledge/doc-status.ts index c48fa7e3..02c6286f 100644 --- a/packages/commands/src/commands/knowledge/doc-status.ts +++ b/packages/commands/src/commands/knowledge/doc-status.ts @@ -22,21 +22,33 @@ const DOC_STATUS_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, jobId: { type: "string", valueHint: "", - description: "Import job ID (ingestionId returned by import commands)", + description: { + "en-US": "Import job ID (ingestionId returned by import commands)", + "zh-CN": "导入任务 ID(导入命令返回的 ingestionId)", + }, required: true, }, ...PAGE_FLAGS, - wait: { type: "switch", description: "Poll until the job reaches a terminal state" }, + wait: { + type: "switch", + description: { + "en-US": "Poll until the job reaches a terminal state", + "zh-CN": "轮询直到任务进入终态", + }, + }, pollInterval: { type: "number", valueHint: "", - description: "Polling interval when waiting (default: 5)", + description: { + "en-US": "Polling interval when waiting (default: 5)", + "zh-CN": "等待时的轮询间隔(默认:5)", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -52,14 +64,29 @@ function printStatus(response: RagIndexJobStatusResponse): void { } export default defineCommand({ - description: "Check knowledge base import job status", + description: { + "en-US": "Check knowledge base import job status", + "zh-CN": "检查知识库导入任务状态", + }, auth: "apiKey", usageArgs: "--index-id --job-id [flags]", flags: DOC_STATUS_FLAGS, notes: [ - "Both --index-id and --job-id are required (passing only one returns SystemError).", - "If you see a SystemError, the job may not exist — check the ingestion id in the document list output.", - "Overall job states are PENDING / RUNNING / COMPLETED; per-document failures (for example PARSE_FAILED) exit non-zero with the error message passed through.", + { + "en-US": "Both --index-id and --job-id are required (passing only one returns SystemError).", + "zh-CN": "--index-id 和 --job-id 均为必填(只传其中一个会返回 SystemError)。", + }, + { + "en-US": + "If you see a SystemError, the job may not exist — check the ingestion id in the document list output.", + "zh-CN": "如果出现 SystemError,任务可能不存在——请检查文档列表输出中的 ingestion ID。", + }, + { + "en-US": + "Overall job states are PENDING / RUNNING / COMPLETED; per-document failures (for example PARSE_FAILED) exit non-zero with the error message passed through.", + "zh-CN": + "任务整体状态为 PENDING / RUNNING / COMPLETED;单个文档失败(例如 PARSE_FAILED)时会以非零状态退出,并原样透传错误信息。", + }, ], exampleArgs: [ "--index-id idx-xxx --job-id job-xxx --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/doc-tag.ts b/packages/commands/src/commands/knowledge/doc-tag.ts index 55a31076..8e252a69 100644 --- a/packages/commands/src/commands/knowledge/doc-tag.ts +++ b/packages/commands/src/commands/knowledge/doc-tag.ts @@ -13,31 +13,52 @@ const DOC_TAG_FLAGS = { docId: { type: "array", valueHint: "", - description: "Data-center file ID to tag (repeatable, 1-20 per call)", + description: { + "en-US": "Data-center file ID to tag (repeatable, 1-20 per call)", + "zh-CN": "要添加标签的数据中心文件 ID(可重复,每次调用 1–20 个)", + }, required: true, }, tag: { type: "array", valueHint: "", - description: "Tag applied to every --doc-id (repeatable, each up to 32 chars)", + description: { + "en-US": "Tag applied to every --doc-id (repeatable, each up to 32 chars)", + "zh-CN": "应用于每个 --doc-id 的标签(可重复,每个最多 32 个字符)", + }, required: true, }, mode: { type: "string", valueHint: "", - description: "Update mode: append (default) or overwrite", + description: { + "en-US": "Update mode: append (default) or overwrite", + "zh-CN": "更新模式:append(默认)或 overwrite", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Batch update tags on data-center files", + description: { + "en-US": "Batch update tags on data-center files", + "zh-CN": "批量更新数据中心文件标签", + }, auth: "apiKey", usageArgs: "--doc-id --tag [flags]", flags: DOC_TAG_FLAGS, notes: [ - "The same tag set is applied to every --doc-id; run the command multiple times for different tag sets.", - "Server limits: up to 100 tags per file, total tag length up to 700 chars, tag up to 32 chars.", + { + "en-US": + "The same tag set is applied to every --doc-id; run the command multiple times for different tag sets.", + "zh-CN": "同一组标签会应用于每个 --doc-id;若需应用不同标签组,请多次运行该命令。", + }, + { + "en-US": + "Server limits: up to 100 tags per file, total tag length up to 700 chars, tag up to 32 chars.", + "zh-CN": + "服务端限制:每个文件最多 100 个标签,标签总长度最多 700 个字符,单个标签最多 32 个字符。", + }, ], exampleArgs: [ "--doc-id file-xxx --tag project-a --tag draft --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/doc-upload.ts b/packages/commands/src/commands/knowledge/doc-upload.ts index b81e1ca6..885c3b65 100644 --- a/packages/commands/src/commands/knowledge/doc-upload.ts +++ b/packages/commands/src/commands/knowledge/doc-upload.ts @@ -31,33 +31,51 @@ const DOC_UPLOAD_FLAGS = { file: { type: "array", valueHint: "", - description: - "Local file or directory path (repeatable). Directories are scanned recursively; unsupported formats are skipped", + description: { + "en-US": + "Local file or directory path (repeatable). Directories are scanned recursively; unsupported formats are skipped", + "zh-CN": "本地文件或目录路径(可重复)。目录会递归扫描,不支持的格式将被跳过", + }, required: true, }, indexId: { type: "string", valueHint: "", - description: "Import into this knowledge base after registration (one job for all files)", + description: { + "en-US": "Import into this knowledge base after registration (one job for all files)", + "zh-CN": "文件注册后导入该知识库(所有文件共用一个任务)", + }, }, categoryId: { type: "string", valueHint: "", - description: "Target data-center category; defaults to the workspace default category", + description: { + "en-US": "Target data-center category; defaults to the workspace default category", + "zh-CN": "目标数据中心类目;默认为 Workspace 的默认类目", + }, }, tag: { type: "array", valueHint: "", - description: "File tag (repeatable), applied to every uploaded file", + description: { + "en-US": "File tag (repeatable), applied to every uploaded file", + "zh-CN": "文件标签(可重复),应用于每个上传文件", + }, }, wait: { type: "switch", - description: "Poll the import job to a terminal state (needs --index-id)", + description: { + "en-US": "Poll the import job to a terminal state (needs --index-id)", + "zh-CN": "轮询导入任务直到进入终态(需要 --index-id)", + }, }, pollInterval: { type: "number", valueHint: "", - description: "Polling interval when waiting (default: 5)", + description: { + "en-US": "Polling interval when waiting (default: 5)", + "zh-CN": "等待时的轮询间隔(默认:5)", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -68,16 +86,35 @@ interface UploadedFile { } export default defineCommand({ - description: - "Upload local files or directories to the data center and optionally import into a knowledge base", + description: { + "en-US": + "Upload local files or directories to the data center and optionally import into a knowledge base", + "zh-CN": "上传本地文件或目录到数据中心,并可选择导入知识库", + }, auth: "apiKey", usageArgs: "--file [flags]", flags: DOC_UPLOAD_FLAGS, notes: [ - "Pipeline: apply upload lease → PUT to OSS → register file → (with --index-id) create import job.", - "Without --category-id the workspace default category is resolved automatically.", - "Directories are scanned recursively; node_modules, .git, and similar are skipped automatically.", - "Multiple files are processed sequentially; on failure, already-registered file ids are listed in the error hint.", + { + "en-US": + "Pipeline: apply upload lease → PUT to OSS → register file → (with --index-id) create import job.", + "zh-CN": + "处理流程:申请上传凭证 → PUT 到 OSS → 注册文件 →(传入 --index-id 时)创建导入任务。", + }, + { + "en-US": "Without --category-id the workspace default category is resolved automatically.", + "zh-CN": "未传入 --category-id 时,会自动解析 Workspace 的默认类目。", + }, + { + "en-US": + "Directories are scanned recursively; node_modules, .git, and similar are skipped automatically.", + "zh-CN": "目录会递归扫描;node_modules、.git 等目录会被自动跳过。", + }, + { + "en-US": + "Multiple files are processed sequentially; on failure, already-registered file ids are listed in the error hint.", + "zh-CN": "多个文件会依次处理;失败时,错误提示会列出已经注册成功的文件 ID。", + }, ], exampleArgs: [ "--file ./a.md --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/file-delete.ts b/packages/commands/src/commands/knowledge/file-delete.ts index d9986ddb..f73b94ca 100644 --- a/packages/commands/src/commands/knowledge/file-delete.ts +++ b/packages/commands/src/commands/knowledge/file-delete.ts @@ -15,10 +15,16 @@ const FILE_DELETE_FLAGS = { fileId: { type: "string", valueHint: "", - description: "Data-center file ID to delete", + description: { + "en-US": "Data-center file ID to delete", + "zh-CN": "要删除的数据中心文件 ID", + }, required: true, }, - yes: { type: "switch", description: "Skip the confirmation prompt" }, + yes: { + type: "switch", + description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" }, + }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -43,13 +49,24 @@ async function buildDeleteSummary( } export default defineCommand({ - description: "Permanently delete a file from the data center", + description: { + "en-US": "Permanently delete a file from the data center", + "zh-CN": "从数据中心永久删除文件", + }, auth: "apiKey", usageArgs: "--file-id [flags]", flags: FILE_DELETE_FLAGS, notes: [ - "Irreversible. If knowledge bases reference this file, their related document indexes become invalid.", - "To remove a document from a single knowledge base only, use the document delete command instead.", + { + "en-US": + "Irreversible. If knowledge bases reference this file, their related document indexes become invalid.", + "zh-CN": "该操作不可撤销。如果知识库引用了此文件,其相关文档索引将失效。", + }, + { + "en-US": + "To remove a document from a single knowledge base only, use the document delete command instead.", + "zh-CN": "如果只需从单个知识库中移除文档,请改用文档删除命令。", + }, ], exampleArgs: ["--file-id file-xxx --workspace-id ws-xxx", "--file-id file-xxx --yes"], async run(ctx) { diff --git a/packages/commands/src/commands/knowledge/file-get.ts b/packages/commands/src/commands/knowledge/file-get.ts index 3ff414fc..81bd750f 100644 --- a/packages/commands/src/commands/knowledge/file-get.ts +++ b/packages/commands/src/commands/knowledge/file-get.ts @@ -13,14 +13,17 @@ const FILE_GET_FLAGS = { fileId: { type: "string", valueHint: "", - description: "Data-center file ID", + description: { "en-US": "Data-center file ID", "zh-CN": "数据中心文件 ID" }, required: true, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "Show data-center file details (size, MD5, tags, timestamps)", + description: { + "en-US": "Show data-center file details (size, MD5, tags, timestamps)", + "zh-CN": "查看数据中心文件详情(大小、MD5、标签、时间戳)", + }, auth: "apiKey", usageArgs: "--file-id [flags]", flags: FILE_GET_FLAGS, diff --git a/packages/commands/src/commands/knowledge/file-list.ts b/packages/commands/src/commands/knowledge/file-list.ts index ac7fe902..9f9c764d 100644 --- a/packages/commands/src/commands/knowledge/file-list.ts +++ b/packages/commands/src/commands/knowledge/file-list.ts @@ -13,45 +13,74 @@ const FILE_LIST_FLAGS = { categoryId: { type: "string", valueHint: "", - description: "Category to list (find ids via the category list command); exact match", + description: { + "en-US": "Category to list (find ids via the category list command); exact match", + "zh-CN": "要列出文件的类目(通过类目列表命令查找 ID);精确匹配", + }, required: true, }, name: { type: "string", valueHint: "", - description: "Filter by exact file name without its extension (a.md → pass a)", + description: { + "en-US": "Filter by exact file name without its extension (a.md → pass a)", + "zh-CN": "按不含扩展名的文件名精确筛选(a.md 应传入 a)", + }, }, fileId: { type: "array", valueHint: "", - description: "Filter by exact file ID (repeatable)", + description: { + "en-US": "Filter by exact file ID (repeatable)", + "zh-CN": "按文件 ID 精确筛选(可重复)", + }, }, nextToken: { type: "string", valueHint: "", - description: "Cursor for the next page (from previous output)", + description: { + "en-US": "Cursor for the next page (from previous output)", + "zh-CN": "下一页游标(来自上一次输出)", + }, }, maxResult: { type: "number", valueHint: "", - description: "Items per page", + description: { "en-US": "Items per page", "zh-CN": "每页条目数" }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "List files in a data-center category", + description: { + "en-US": "List files in a data-center category", + "zh-CN": "列出数据中心类目中的文件", + }, auth: "apiKey", usageArgs: "--category-id [flags]", flags: FILE_LIST_FLAGS, notes: [ - "A real category id is required — the default value is not resolved here. Find the id via the category list command.", - "--name matches the exact file name without its extension (for a.md pass a); partial keywords return no results.", - "Pagination is cursor-based: reuse the printed next token to continue.", + { + "en-US": + "A real category id is required — the default value is not resolved here. Find the id via the category list command.", + "zh-CN": "必须提供真实的类目 ID——此处不会解析 default 值。请通过类目列表命令查找 ID。", + }, + { + "en-US": + "--name matches the exact file name without its extension (for a.md pass a); partial keywords return no results.", + "zh-CN": "--name 精确匹配不含扩展名的文件名(a.md 应传入 a);部分关键字不会返回结果。", + }, + { + "en-US": "Pagination is cursor-based: reuse the printed next token to continue.", + "zh-CN": "分页使用游标:复用输出中的 next token 继续查询。", + }, ], exampleArgs: [ "--category-id cate-xxx --workspace-id ws-xxx", - "--category-id cate-xxx --name report", + { + "en-US": "--category-id cate-xxx --name report", + "zh-CN": "--category-id cate-xxx --name 报告", + }, ], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/knowledge/kb-create.ts b/packages/commands/src/commands/knowledge/kb-create.ts index 474f1152..25aa960a 100644 --- a/packages/commands/src/commands/knowledge/kb-create.ts +++ b/packages/commands/src/commands/knowledge/kb-create.ts @@ -23,36 +23,59 @@ const KB_CREATE_FLAGS = { name: { type: "string", valueHint: "", - description: "Knowledge base name (1-20 chars, unique in workspace)", + description: { + "en-US": "Knowledge base name (1-20 chars, unique in workspace)", + "zh-CN": "知识库名称(1–20 个字符,在 Workspace 中唯一)", + }, required: true, }, docId: { type: "array", valueHint: "", - description: - "Data-center file id to import (repeatable); mutually exclusive with --category-id", + description: { + "en-US": "Data-center file id to import (repeatable); mutually exclusive with --category-id", + "zh-CN": "要导入的数据中心文件 ID(可重复);不能与 --category-id 同时使用", + }, }, categoryId: { type: "array", valueHint: "", - description: - "Import every file under this category (repeatable); mutually exclusive with --doc-id", + description: { + "en-US": + "Import every file under this category (repeatable); mutually exclusive with --doc-id", + "zh-CN": "导入该类目下的所有文件(可重复);不能与 --doc-id 同时使用", + }, }, embeddingModel: { type: "string", valueHint: "", - description: "Embedding model name (default: text-embedding-v4)", + description: { + "en-US": "Embedding model name (default: text-embedding-v4)", + "zh-CN": "Embedding 模型名称(默认:text-embedding-v4)", + }, }, chunkSize: { type: "number", valueHint: "", - description: "Chunk size in characters (default: 600, recommended 300-800)", + description: { + "en-US": "Chunk size in characters (default: 600, recommended 300-800)", + "zh-CN": "Chunk 字符数(默认:600,建议:300–800)", + }, + }, + wait: { + type: "switch", + description: { + "en-US": "Poll the initial import job to a terminal state", + "zh-CN": "轮询初始导入任务直到进入终态", + }, }, - wait: { type: "switch", description: "Poll the initial import job to a terminal state" }, pollInterval: { type: "number", valueHint: "", - description: "Polling interval when waiting (default: 5)", + description: { + "en-US": "Polling interval when waiting (default: 5)", + "zh-CN": "等待时的轮询间隔(默认:5)", + }, }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -79,14 +102,28 @@ export function buildDataSourceFields(flags: { docId?: string[]; categoryId?: st } export default defineCommand({ - description: "Create a knowledge base and import data-center files or categories", + description: { + "en-US": "Create a knowledge base and import data-center files or categories", + "zh-CN": "创建知识库并导入数据中心文件或类目", + }, auth: "apiKey", usageArgs: "--name (--doc-id | --category-id ) [flags]", flags: KB_CREATE_FLAGS, notes: [ - "Structure/sink types are fixed to the default document knowledge base (unstructured, BUILT_IN storage).", - "Returns the knowledge base id (pipelineId) and the initial import job id (ingestionId).", - "Use the import job status command (or --wait) to track the initial import.", + { + "en-US": + "Structure/sink types are fixed to the default document knowledge base (unstructured, BUILT_IN storage).", + "zh-CN": "结构和存储类型固定为默认文档知识库(unstructured、BUILT_IN 存储)。", + }, + { + "en-US": + "Returns the knowledge base id (pipelineId) and the initial import job id (ingestionId).", + "zh-CN": "返回知识库 ID(pipelineId)和初始导入任务 ID(ingestionId)。", + }, + { + "en-US": "Use the import job status command (or --wait) to track the initial import.", + "zh-CN": "使用导入任务状态命令(或 --wait)跟踪初始导入。", + }, ], exampleArgs: [ "--name demo --doc-id file-xxx --workspace-id ws-xxx", diff --git a/packages/commands/src/commands/knowledge/kb-delete.ts b/packages/commands/src/commands/knowledge/kb-delete.ts index 2f9ddb5e..945173e9 100644 --- a/packages/commands/src/commands/knowledge/kb-delete.ts +++ b/packages/commands/src/commands/knowledge/kb-delete.ts @@ -15,10 +15,13 @@ const KB_DELETE_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, - yes: { type: "switch", description: "Skip the confirmation prompt" }, + yes: { + type: "switch", + description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" }, + }, ...WORKSPACE_FLAG, } satisfies FlagsDef; @@ -54,13 +57,23 @@ async function buildDeleteSummary( } export default defineCommand({ - description: "Delete a knowledge base with all its documents and chunks", + description: { + "en-US": "Delete a knowledge base with all its documents and chunks", + "zh-CN": "删除知识库及其所有文档和 Chunk", + }, auth: "apiKey", usageArgs: "--index-id [flags]", flags: KB_DELETE_FLAGS, notes: [ - "Irreversible — the knowledge base and all indexed content are permanently removed.", - "Files in the data center are not affected; only the knowledge base index is deleted.", + { + "en-US": "Irreversible — the knowledge base and all indexed content are permanently removed.", + "zh-CN": "该操作不可撤销——知识库及所有已索引内容将被永久删除。", + }, + { + "en-US": + "Files in the data center are not affected; only the knowledge base index is deleted.", + "zh-CN": "数据中心中的文件不受影响;仅删除知识库索引。", + }, ], exampleArgs: ["--index-id idx-xxx --workspace-id ws-xxx", "--index-id idx-xxx --yes"], async run(ctx) { diff --git a/packages/commands/src/commands/knowledge/kb-info.ts b/packages/commands/src/commands/knowledge/kb-info.ts index cbb1dc1b..f5fbe793 100644 --- a/packages/commands/src/commands/knowledge/kb-info.ts +++ b/packages/commands/src/commands/knowledge/kb-info.ts @@ -17,7 +17,7 @@ const KB_INFO_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, ...WORKSPACE_FLAG, @@ -61,11 +61,20 @@ function formatField(label: string, value: string | number | boolean | null | un } export default defineCommand({ - description: "Show knowledge base configuration details", + description: { + "en-US": "Show knowledge base configuration details", + "zh-CN": "查看知识库配置详情", + }, auth: "apiKey", usageArgs: "--index-id [flags]", flags: KB_INFO_FLAGS, - notes: ["Indexing settings are immutable; changing them requires recreating the knowledge base."], + notes: [ + { + "en-US": + "Indexing settings are immutable; changing them requires recreating the knowledge base.", + "zh-CN": "索引设置不可修改;如需更改,必须重新创建知识库。", + }, + ], exampleArgs: ["--index-id idx-xxx --workspace-id ws-xxx"], async run(ctx) { const { settings, flags } = ctx; diff --git a/packages/commands/src/commands/knowledge/kb-list.ts b/packages/commands/src/commands/knowledge/kb-list.ts index 8e322dfd..32489bcd 100644 --- a/packages/commands/src/commands/knowledge/kb-list.ts +++ b/packages/commands/src/commands/knowledge/kb-list.ts @@ -13,21 +13,37 @@ const KB_LIST_FLAGS = { name: { type: "string", valueHint: "", - description: "Filter by knowledge base name (fuzzy match, 1-20 chars)", + description: { + "en-US": "Filter by knowledge base name (fuzzy match, 1-20 chars)", + "zh-CN": "按知识库名称筛选(模糊匹配,1–20 个字符)", + }, }, ...PAGE_FLAGS, ...WORKSPACE_FLAG, } satisfies FlagsDef; export default defineCommand({ - description: "List knowledge bases in the workspace", + description: { + "en-US": "List knowledge bases in the workspace", + "zh-CN": "列出 Workspace 中的知识库", + }, auth: "apiKey", usageArgs: "[flags]", flags: KB_LIST_FLAGS, notes: [ - "Auth: uses DashScope API Key (Bearer token).", - "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or config workspace_id.", - "Use the returned id as --index-id in knowledge base / document management commands.", + { + "en-US": "Auth: uses DashScope API Key (Bearer token).", + "zh-CN": "鉴权:使用 DashScope API Key(Bearer Token)。", + }, + { + "en-US": "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or config workspace_id.", + "zh-CN": "`--workspace-id` 可通过 BAILIAN_WORKSPACE_ID 环境变量或配置项 workspace_id 设置。", + }, + { + "en-US": + "Use the returned id as --index-id in knowledge base / document management commands.", + "zh-CN": "在知识库/文档管理命令中,将返回的 ID 作为 --index-id 使用。", + }, ], exampleArgs: ["--workspace-id ws-xxx", "--name demo --page-number 2 --page-size 50"], validate(flags) { diff --git a/packages/commands/src/commands/knowledge/kb-stats.ts b/packages/commands/src/commands/knowledge/kb-stats.ts index 5bb13a61..ff9f7e34 100644 --- a/packages/commands/src/commands/knowledge/kb-stats.ts +++ b/packages/commands/src/commands/knowledge/kb-stats.ts @@ -15,19 +15,24 @@ const KB_STATS_FLAGS = { indexId: { type: "string", valueHint: "", - description: "Knowledge base ID", + description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" }, required: true, }, start: { type: "string", valueHint: "