| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- import type { ChatDepth } from "./chat-depth.js";
- export type TuiLocale = "zh-CN" | "en";
- export interface TuiCopy {
- readonly locale: TuiLocale;
- readonly labels: {
- readonly project: string;
- readonly book: string;
- readonly depth: string;
- readonly session: string;
- readonly messageCount: (count: number) => string;
- readonly stage: string;
- readonly mode: string;
- readonly model: string;
- readonly error: string;
- readonly recent: string;
- readonly pending: string;
- readonly ready: string;
- readonly none: string;
- readonly notConfigured: string;
- readonly unknown: string;
- };
- readonly modeLabels: Record<string, string>;
- readonly composer: {
- readonly placeholder: string;
- readonly emptyConversation: string;
- readonly helper: string;
- readonly submitting: string;
- readonly failed: string;
- readonly ready: string;
- };
- readonly notes: {
- readonly help: string;
- readonly status: (stage: string, mode: string) => string;
- readonly config: string;
- readonly depthSet: (depthLabel: string) => string;
- readonly noLlmConfig: string;
- readonly setupProvider: string;
- readonly toolInitFailed: (message: string) => string;
- readonly toolInitHint: string;
- };
- readonly roles: {
- readonly user: string;
- readonly assistant: string;
- readonly system: string;
- };
- readonly activity: Record<"thinking" | "checking" | "writing" | "reviewing" | "updating", string>;
- readonly depthLabels: Record<ChatDepth, string>;
- readonly results: {
- readonly modeSwitched: (mode: string) => string;
- readonly booksListed: string;
- readonly activeBook: (bookId: string) => string;
- readonly completed: (intent: string) => string;
- readonly intentLabels: Partial<Record<string, string>>;
- };
- }
- const ZH_CN: TuiCopy = {
- locale: "zh-CN",
- labels: {
- project: "项目",
- book: "作品",
- depth: "深度",
- session: "会话",
- messageCount: (count) => `${count} 条消息`,
- stage: "阶段",
- mode: "模式",
- model: "模型",
- error: "错误",
- recent: "最近",
- pending: "待确认",
- ready: "就绪",
- none: "无",
- notConfigured: "未配置",
- unknown: "未知",
- },
- modeLabels: {
- auto: "自动",
- semi: "半自动",
- manual: "手动",
- },
- composer: {
- placeholder: "告诉 InkOS 要写什么、修改什么,或解释什么…",
- emptyConversation: "先告诉 InkOS 你要做什么。",
- helper: "回车发送 • /help • /status • /clear • /config • /depth • /quit",
- submitting: "处理中…",
- failed: "上次请求失败",
- ready: "就绪",
- },
- notes: {
- help: "可用命令:/help、/status、/clear、/config、/depth、/quit。也支持直接输入自然语言。",
- status: (stage, mode) => `当前状态:${stage}(${mode})。`,
- config: "当前 Ink 仪表盘里还不支持交互式 /config。请使用 inkos config set-global。",
- depthSet: (depthLabel) => `思考深度已切换为 ${depthLabel}。`,
- noLlmConfig: "未发现 LLM 配置。",
- setupProvider: "先配置 API 提供方。",
- toolInitFailed: (message) => `初始化 TUI 工具失败:${message}`,
- toolInitHint: "请检查 .env,或运行:inkos config set-global",
- },
- roles: {
- user: "你",
- assistant: "InkOS",
- system: "系统",
- },
- activity: {
- thinking: "思考中",
- checking: "检查中",
- writing: "写作中",
- reviewing: "审阅中",
- updating: "更新中",
- },
- depthLabels: {
- light: "轻量",
- normal: "标准",
- deep: "深入",
- },
- results: {
- modeSwitched: (mode) => `已切换到 ${mode} 模式。`,
- booksListed: "已列出作品。",
- activeBook: (bookId) => `当前作品:${bookId}`,
- completed: (intent) => `已完成 ${intent}`,
- intentLabels: {
- write_next: "已写完下一章",
- revise_chapter: "已修订章节",
- rewrite_chapter: "已重写章节",
- update_focus: "已更新焦点",
- explain_status: "状态说明",
- explain_failure: "失败说明",
- pause_book: "已暂停作品",
- rename_entity: "已重命名实体",
- patch_chapter_text: "已修补正文",
- edit_truth: "已更新真相文件",
- },
- },
- };
- const EN: TuiCopy = {
- locale: "en",
- labels: {
- project: "Project",
- book: "Book",
- depth: "Depth",
- session: "Session",
- messageCount: (count) => `${count} msgs`,
- stage: "Stage",
- mode: "Mode",
- model: "Model",
- error: "Error",
- recent: "Recent",
- pending: "Pending",
- ready: "Ready",
- none: "none",
- notConfigured: "not configured",
- unknown: "unknown",
- },
- modeLabels: {
- auto: "auto",
- semi: "semi",
- manual: "manual",
- },
- composer: {
- placeholder: "Ask InkOS to write, revise, or explain…",
- emptyConversation: "Start by asking InkOS what to do.",
- helper: "Enter to send • /help • /status • /clear • /config • /depth • /quit",
- submitting: "Submitting…",
- failed: "Last request failed",
- ready: "Ready",
- },
- notes: {
- help: "Commands: /help, /status, /clear, /config, /depth, /quit. Natural language still works.",
- status: (stage, mode) => `Status: ${stage} (${mode}).`,
- config: "Interactive /config is not available inside the Ink dashboard yet. Use inkos config set-global.",
- depthSet: (depthLabel) => `Thinking depth set to ${depthLabel}.`,
- noLlmConfig: "No LLM configuration found.",
- setupProvider: "Let's set up your API provider first.",
- toolInitFailed: (message) => `Failed to initialize TUI tools: ${message}`,
- toolInitHint: "Check your .env or run: inkos config set-global",
- },
- roles: {
- user: "You",
- assistant: "InkOS",
- system: "System",
- },
- activity: {
- thinking: "thinking",
- checking: "checking",
- writing: "writing",
- reviewing: "reviewing",
- updating: "updating",
- },
- depthLabels: {
- light: "light",
- normal: "normal",
- deep: "deep",
- },
- results: {
- modeSwitched: (mode) => `Mode switched to ${mode}.`,
- booksListed: "Books listed.",
- activeBook: (bookId) => `Active book: ${bookId}`,
- completed: (intent) => `Completed ${intent}`,
- intentLabels: {
- write_next: "Chapter written",
- revise_chapter: "Chapter revised",
- rewrite_chapter: "Chapter rewritten",
- update_focus: "Focus updated",
- explain_status: "Status",
- explain_failure: "Explanation",
- pause_book: "Book paused",
- rename_entity: "Entity renamed",
- patch_chapter_text: "Text patched",
- edit_truth: "Truth file updated",
- },
- },
- };
- export function resolveTuiLocale(
- env: NodeJS.ProcessEnv = process.env,
- preferredLanguage?: string,
- ): TuiLocale {
- const requested = normalizeLocale(env.INKOS_TUI_LOCALE ?? env.INKOS_LOCALE);
- if (requested) {
- return requested;
- }
- const preferred = normalizeLocale(preferredLanguage);
- if (preferred) {
- return preferred;
- }
- const detected = normalizeLocale(env.LC_ALL ?? env.LC_MESSAGES ?? env.LANG);
- return detected ?? "zh-CN";
- }
- export function getTuiCopy(locale: TuiLocale): TuiCopy {
- return locale === "en" ? EN : ZH_CN;
- }
- export function normalizeStageLabel(label: string, copy: TuiCopy): string {
- const normalized = label.trim().toLowerCase();
- if (!normalized) {
- return label;
- }
- const replacements: Array<[RegExp, string]> = [
- [/^thinking\b/i, copy.activity.thinking],
- [/^checking\b/i, copy.activity.checking],
- [/^writing\b/i, copy.activity.writing],
- [/^reviewing\b/i, copy.activity.reviewing],
- [/^updating\b/i, copy.activity.updating],
- [/^completed\b/i, copy.locale === "en" ? "completed" : "已完成"],
- [/^failed\b/i, copy.locale === "en" ? "failed" : "失败"],
- [/^blocked\b/i, copy.locale === "en" ? "blocked" : "已阻塞"],
- [/^waiting_human\b/i, copy.locale === "en" ? "waiting for your decision" : "等待你的决定"],
- [/^paused by user\b/i, copy.locale === "en" ? "paused by user" : "已由用户暂停"],
- [/^ready to continue\b/i, copy.locale === "en" ? "ready to continue" : "可继续执行"],
- ];
- for (const [pattern, value] of replacements) {
- if (pattern.test(label)) {
- return copy.locale === "en" ? label : value;
- }
- }
- if (normalized === "idle") {
- return copy.labels.ready;
- }
- return label;
- }
- export function formatModeLabel(mode: string, copy: TuiCopy): string {
- return copy.modeLabels[mode] ?? mode;
- }
- function normalizeLocale(value: string | undefined): TuiLocale | undefined {
- if (!value) {
- return undefined;
- }
- const normalized = value.trim().toLowerCase();
- if (!normalized || normalized === "auto") {
- return undefined;
- }
- if (normalized.startsWith("zh")) {
- return "zh-CN";
- }
- if (normalized.startsWith("en")) {
- return "en";
- }
- return undefined;
- }
|