@@ -8,3 +8,3 @@ import { Command, Args } from '@oclif/core'; | ||
| import ora from 'ora'; | ||
| import { startRepl, handleUseCommand } from '../ui/repl.js'; | ||
| import { startRepl, handleUseCommand, handleCommand } from '../ui/repl.js'; | ||
| import { confirm } from '@inquirer/prompts'; | ||
@@ -26,2 +26,7 @@ export default class Main extends Command { | ||
| } | ||
| // Direct command execution (e.g. "t /target en" or "t /status") | ||
| if (inputText.startsWith('/')) { | ||
| await handleCommand(inputText, configService); | ||
| return; | ||
| } | ||
| const spinner = ora('Translating...').start(); | ||
@@ -28,0 +33,0 @@ try { |
@@ -32,7 +32,2 @@ import Conf from 'conf'; | ||
| }, | ||
| 'openai': { | ||
| type: 'openai', | ||
| model: 'gpt-4o', | ||
| apiKey: '', | ||
| }, | ||
| 'lmstudio': { | ||
@@ -44,2 +39,7 @@ type: 'lmstudio', | ||
| }, | ||
| 'openai': { | ||
| type: 'openai', | ||
| model: 'gpt-4o', | ||
| apiKey: '', | ||
| }, | ||
| 'deepseek': { | ||
@@ -46,0 +46,0 @@ type: 'deepseek', |
| import { ConfigService } from '../services/config.js'; | ||
| export declare function startRepl(configService: ConfigService, version: string): Promise<void>; | ||
| export declare function handleCommand(cmd: string, configService: ConfigService): Promise<boolean>; | ||
| export declare function handleUseCommand(configService: ConfigService): Promise<void>; |
+63
-10
@@ -10,5 +10,3 @@ import chalk from 'chalk'; | ||
| let lastWord = ""; | ||
| export async function startRepl(configService, version) { | ||
| const historyService = new HistoryService(); | ||
| console.log(chalk.cyan(`Welcome to xtrans v${version}`)); | ||
| function printHeader(configService) { | ||
| const config = configService.config; | ||
@@ -20,2 +18,7 @@ const currentProvider = configService.getCurrentService(); | ||
| } | ||
| } | ||
| export async function startRepl(configService, version) { | ||
| const historyService = new HistoryService(); | ||
| console.log(chalk.cyan(`Welcome to xtrans v${version}`)); | ||
| printHeader(configService); | ||
| console.log(chalk.dim('Type "/" to open menu. Start typing to translate.\n')); | ||
@@ -28,3 +31,3 @@ // Custom Input Loop with TTY handling | ||
| // 2. Read input manually to catch '/' | ||
| const line = await readLineOrCommand(); | ||
| const line = await readLineOrCommand(historyService.getHistory()); | ||
| if (!line) | ||
@@ -49,3 +52,3 @@ continue; // Empty line | ||
| */ | ||
| function readLineOrCommand() { | ||
| function readLineOrCommand(history = []) { | ||
| return new Promise((resolve) => { | ||
@@ -57,2 +60,4 @@ const { stdin, stdout } = process; | ||
| let buffer = ''; | ||
| let historyIndex = -1; | ||
| let tempBuffer = ''; // Stores current typing when moving up into history | ||
| const onData = async (key) => { | ||
@@ -74,2 +79,37 @@ // Ctrl+C (ETX) | ||
| } | ||
| // Up Arrow | ||
| if (key === '\u001b[A') { | ||
| if (history.length > 0 && historyIndex < history.length - 1) { | ||
| if (historyIndex === -1) | ||
| tempBuffer = buffer; | ||
| historyIndex++; | ||
| buffer = history[historyIndex]; | ||
| // Redraw line | ||
| stdout.clearLine(0); | ||
| stdout.cursorTo(0); | ||
| stdout.write(chalk.cyan('› ') + buffer); | ||
| } | ||
| return; | ||
| } | ||
| // Down Arrow | ||
| if (key === '\u001b[B') { | ||
| if (historyIndex > -1) { | ||
| historyIndex--; | ||
| if (historyIndex === -1) { | ||
| buffer = tempBuffer; | ||
| } | ||
| else { | ||
| buffer = history[historyIndex]; | ||
| } | ||
| // Redraw line | ||
| stdout.clearLine(0); | ||
| stdout.cursorTo(0); | ||
| stdout.write(chalk.cyan('› ') + buffer); | ||
| } | ||
| return; | ||
| } | ||
| // Left/Right Arrow - Ignore for now to prevent corruption | ||
| if (key === '\u001b[C' || key === '\u001b[D') { | ||
| return; | ||
| } | ||
| // Backspace (DEL) | ||
@@ -148,3 +188,3 @@ if (key === '\x7f') { | ||
| } | ||
| async function handleCommand(cmd, configService) { | ||
| export async function handleCommand(cmd, configService) { | ||
| const [command, arg] = cmd.split(' '); | ||
@@ -392,3 +432,7 @@ switch (command) { | ||
| const services = configService.listServices(); | ||
| console.log(chalk.bold('\nServices Status:')); | ||
| const currentService = configService.config.current; | ||
| // Use the standard header format | ||
| console.log(''); // Spacing | ||
| printHeader(configService); | ||
| console.log(chalk.bold('\nServices Availability:')); | ||
| for (const [name, config] of Object.entries(services)) { | ||
@@ -398,6 +442,15 @@ const provider = ProviderFactory.create(name, config); | ||
| const isHealthy = await provider.checkHealth(); | ||
| process.stdout.clearLine(0); | ||
| process.stdout.cursorTo(0); | ||
| if (process.stdout.isTTY) { | ||
| process.stdout.clearLine(0); | ||
| process.stdout.cursorTo(0); | ||
| } | ||
| else { | ||
| process.stdout.write('\n'); | ||
| } | ||
| const status = isHealthy ? chalk.green('● Online') : chalk.red('○ Offline'); | ||
| console.log(` ${name}: ${status}`); | ||
| const isActive = name === currentService ? chalk.blue(' (active)') : ''; | ||
| // We don't need to duplicate model info here since it's in the header for the *current* one | ||
| // But for others, maybe we just show status? | ||
| // Let's keep it minimal as requested. | ||
| console.log(` ${name}: ${status}${isActive}`); | ||
| } | ||
@@ -404,0 +457,0 @@ console.log(''); |
@@ -31,3 +31,3 @@ { | ||
| }, | ||
| "version": "1.0.0" | ||
| "version": "1.0.1" | ||
| } |
+1
-1
| { | ||
| "name": "xtrans", | ||
| "description": "Minimalist AI-powered terminal translator / 极简 AI 终端翻译工具", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "author": "wengqianshan", | ||
@@ -6,0 +6,0 @@ "bin": { |
+66
-31
@@ -1,6 +0,8 @@ | ||
| # xtrans (Open Translate) | ||
| # xtrans | ||
| Minimalist AI-powered terminal translator. | ||
| 极简 AI 终端翻译工具。 | ||
| 极简主义的 AI 终端翻译工具。 | ||
| 支持本地大模型 (Ollama, LM Studio) 与主流在线 API。 | ||
| **中文** | [English](README_EN.md) | ||
| [](https://npmjs.org/package/xtrans) | ||
@@ -10,11 +12,11 @@ [](https://npmjs.org/package/xtrans) | ||
| ## Features | ||
| ## 核心亮点 | ||
| - **Minimalist**: Just type `xtrans <text>` (or alias `t`) to translate. | ||
| - **AI-Powered**: Uses OpenAI's GPT models for accurate and context-aware translations. | ||
| - **Interactive Mode**: Enter a REPL mode for continuous translation. | ||
| - **Stream Output**: Real-time translation streaming. | ||
| - **Configuration**: Easy setup with your API key. | ||
| - 🔒 **本地模型支持**: 完美支持 **Ollama** 和 **LM Studio**。无需联网,保护隐私。 | ||
| - 🌍 **多服务商**: 内置 **OpenAI**, **DeepSeek**, **Kimi (Moonshot)** 以及 **Ollama/LM Studio** 支持。 | ||
| - 🛠 **高度可扩展**: 轻松添加任何兼容 **OpenAI 格式** 的 API 服务。 | ||
| - 🤖 **自动发现**: 自动获取服务商的模型列表(例如读取你的 `ollama list`)。 | ||
| - ⚡ **极简体验**: 仅需输入 `t <text>` 即可快速翻译,或进入交互模式连续使用。 | ||
| ## Installation | ||
| ## 安装 | ||
@@ -25,3 +27,3 @@ ```bash | ||
| Or run directly with npx: | ||
| 或者使用 npx 直接运行: | ||
@@ -32,37 +34,70 @@ ```bash | ||
| ## Usage | ||
| ## 使用指南 | ||
| ### Quick Translation | ||
| ### 1. 快速翻译 | ||
| 使用别名 `t` (或 `xtrans`) 进行即时翻译。 | ||
| ### Quick Translation | ||
| Use `xtrans` followed by the text you want to translate. You can also use the shorter alias `t`. | ||
| ```bash | ||
| xtrans hello world | ||
| # or alias | ||
| t hello world | ||
| t hello | ||
| # ┌───────────────────────────────────────────────────┐ | ||
| # │ hello /həˈləʊ/ int. n. │ | ||
| # │ │ | ||
| # │ int. │ | ||
| # │ • 你好 (用于问候、接电话或引起注意) │ | ||
| # │ - Hello, Paul. I haven't seen you for ages. │ | ||
| # │ │ | ||
| # │ n. │ | ||
| # │ • (Hello) 人名;(法)埃洛 │ | ||
| # │ │ | ||
| # │ Synonyms: hi, greetings │ | ||
| # └───────────────────────────────────────────────────┘ | ||
| ``` | ||
| ### Interactive Mode | ||
| ### 2. 交互模式 (REPL) | ||
| 不带参数运行即可进入交互式 shell。支持上下键 (↑/↓) 切换历史记录。 | ||
| Run without arguments to enter interactive mode. | ||
| ```bash | ||
| xtrans | ||
| # > Enter text to translate... | ||
| # Welcome to xtrans v1.0.0 | ||
| # Provider: ollama | Model: llama3 | Target: zh | ||
| # › | ||
| ``` | ||
| ### Help | ||
| ### 3. 管理命令 | ||
| 你可以在交互模式内 (输入 `/`) 或直接从终端运行这些命令。 | ||
| | 命令 | 说明 | 示例 | | ||
| |---------|-------------|---------| | ||
| | `/use` | 切换服务商或模型 (交互式) | `t /use` | | ||
| | `/target` | 设置目标语言 | `t /target ja` | | ||
| | `/status` | 检查服务状态及当前模型 | `t /status` | | ||
| | `/add` | 添加自定义 OpenAI 兼容服务商 | `t /add` | | ||
| | `/remove` | 移除服务商 | `t /remove` | | ||
| | `/clean` | 清理历史记录/缓存 | `t /clean` | | ||
| ## 支持的服务商 | ||
| xtrans 为主流服务预设了配置模板: | ||
| ### 🏠 本地 (隐私优先) | ||
| - **Ollama**: 默认地址 `http://localhost:11434`。支持自动检测已安装模型。 | ||
| - **LM Studio**: 默认地址 `http://localhost:1234/v1`。 | ||
| ### ☁️ 云端 API | ||
| - **OpenAI**: 标准支持。 | ||
| - **DeepSeek**: 深度求索 API 支持。 | ||
| - **Kimi (Moonshot)**: 月之暗面支持。 | ||
| - **Custom (自定义)**: 添加任何支持 OpenAI Chat Completion 格式的服务。 | ||
| ## 配置 | ||
| 首次运行时,xtrans 会引导你设置首选服务商。你可以随时使用 `t /use` 进行切换。 | ||
| 如需添加自定义服务商(例如本地服务器或其他 API): | ||
| ```bash | ||
| xtrans --help | ||
| t /add | ||
| ``` | ||
| ## Configuration | ||
| ## 许可证 | ||
| On first run, the tool will prompt you for your OpenAI API Key and preferred model. Models can be configured via interactive prompts or config files. | ||
| ## License | ||
| MIT |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
58050
8.69%1445
4.26%101
53.03%