-## 📖 项目简介
+## 📖 Project Introduction
-Prompt Optimizer是一个强大的AI提示词优化工具,帮助你编写更好的AI提示词,提升AI输出质量。支持Web应用、桌面应用、Chrome插件和Docker部署四种使用方式。
+Prompt Optimizer is a powerful AI prompt optimization tool that helps you write better AI prompts and improve the quality of AI outputs. It supports four usage methods: web application, desktop application, Chrome extension, and Docker deployment.
-### 🎥 功能演示
+### 🎥 Feature Demonstration
1. Hard-Nosed Reviewer: Turn Agreement into Useful Critique
+
Starting from a minimal English role prompt, optimization pushes a small model away from generic pushback and toward a clearer, more structured review that surfaces weak assumptions, evidence gaps, and concrete revision advice.
2. Marketplace Bargaining Reply: Let Variables Change the Strategy
+
With a single reusable prompt template, you can swap in item details, price anchors, buyer offers, tone, and negotiation goals for different marketplace conversations. After optimization, the same small model does a better job turning those variables into a clearer, more transaction-ready reply instead of a generic helper-style response.
3. Text-to-Image: Optimize a One-Line Idea into a More Directable Key Visual Prompt
+
This is not just prompt expansion. Starting from a vague one-line idea, Prompt Optimizer adds clearer subject cues, spatial relationships, and mood anchors. The left side is simply “a floating library in the night sky,” while the optimized version gives the model a more directed fantasy composition that feels closer to a reusable key visual than a lucky generic image.
+
-## ✨ 核心特性
+## ✨ Core Features
-- 🎯 **智能优化**:一键优化提示词,支持多轮迭代改进,提升AI回复准确度
-- 📝 **双模式优化**:支持系统提示词优化和用户提示词优化,满足不同使用场景
-- 🔄 **分析与对比评估**:支持分析、单结果评估和多结果对比评估,帮助判断提示词是否真的改进
-- 🤖 **多模型集成**:支持OpenAI、Gemini、DeepSeek、智谱AI、SiliconFlow等主流AI模型
-- 🖼️ **图像生成**:支持文生图(T2I)和图生图(I2I),集成Gemini、Seedream等图像模型
-- 📊 **高级测试模式**:上下文变量管理、多轮会话测试、工具调用(Function Calling)支持
-- 🔒 **安全架构**:纯客户端处理,数据直接与AI服务商交互,不经过中间服务器
-- 📱 **多端支持**:同时提供Web应用、桌面应用、Chrome插件和Docker部署四种使用方式
-- 🔐 **访问控制**:支持密码保护功能,保障部署安全
-- 🧩 **MCP协议支持**:支持Model Context Protocol (MCP) 协议,可与Claude Desktop等MCP兼容应用集成
+- 🎯 **Intelligent Optimization**: One-click prompt optimization with multi-round iterative improvements to enhance AI response accuracy
+- 📝 **Dual Mode Optimization**: Support for both system prompt optimization and user prompt optimization to meet different usage scenarios
+- 🔄 **Analysis and Compare Evaluation**: Supports analysis, single-result evaluation, and multi-result compare evaluation to help determine whether a prompt has truly improved
+- 🤖 **Multi-model Integration**: Support for mainstream AI models including OpenAI, Gemini, DeepSeek, Zhipu AI, SiliconFlow, etc.
+- 🖼️ **Image Generation**: Support for Text-to-Image (T2I) and Image-to-Image (I2I) with models like Gemini, Seedream
+- 📊 **Advanced Testing Mode**: Context variable management, multi-turn conversation testing, Function Calling support
+- 🔒 **Secure Architecture**: Pure client-side processing with direct data interaction with AI service providers, bypassing intermediate servers
+- 📱 **Multi-platform Support**: Available as web application, desktop application, Chrome extension, and Docker deployment
+- 🔐 **Access Control**: Password protection feature for secure deployment
+- 🧩 **MCP Protocol Support**: Supports Model Context Protocol (MCP), enabling integration with MCP-compatible AI applications like Claude Desktop
-## 🚀 高级功能
+## 🚀 Advanced Features
-### 图像生成模式
-- 🖼️ **文生图(T2I)**:通过文本提示词生成图像
-- 🎨 **图生图(I2I)**:基于本地图片进行图像变换和优化
-- 🔌 **多模型支持**:集成Gemini、Seedream等主流图像生成模型
-- ⚙️ **模型参数**:支持各模型特有参数配置(如尺寸、风格等)
-- 📥 **预览与下载**:实时预览生成结果,支持下载保存
+### Image Generation Mode
+- 🖼️ **Text-to-Image (T2I)**: Generate images from text prompts
+- 🎨 **Image-to-Image (I2I)**: Transform and optimize images based on local files
+- 🔌 **Multi-model Support**: Integrated with mainstream image generation models like Gemini, Seedream
+- ⚙️ **Model Parameters**: Support model-specific parameter configuration (size, style, etc.)
+- 📥 **Preview & Download**: Real-time preview of generated results with download support
-### 高级测试模式
-- 📊 **上下文变量管理**:自定义变量、批量替换、变量预览
-- 💬 **多轮会话测试**:模拟真实对话场景,测试提示词在多轮交互中的表现
-- 🛠️ **工具调用支持**:Function Calling集成,支持OpenAI和Gemini工具调用
-- 🔍 **分析与评估链路**:在文本模式下支持分析、评估、对比评估和基于评估的智能改写
+### Advanced Testing Mode
+- 📊 **Context Variable Management**: Custom variables, batch replacement, variable preview
+- 💬 **Multi-turn Conversation Testing**: Simulate real conversation scenarios to test prompt performance in multi-turn interactions
+- 🛠️ **Function Calling Support**: Function Calling integration with support for OpenAI and Gemini tool calling
+- 🔍 **Analysis and Evaluation Pipeline**: Supports analysis, evaluation, compare evaluation, and evaluation-driven smart rewrite in text modes
-详细使用说明请查看 [图像模式文档](docs/image-mode.md)
+For detailed usage instructions, please refer to the [Image Mode Documentation](docs/image-mode.md)
-## 快速开始
+## Quick Start
-### 1. 使用在线版本(推荐)
+### 1. Use Online Version (Recommended)
-直接访问:[https://prompt.always200.com](https://prompt.always200.com)
+Direct access: [https://prompt.always200.com](https://prompt.always200.com)
-项目是纯前端项目,所有数据只存储在浏览器本地,不会上传至任何服务器,因此直接使用在线版本也是安全可靠的
+This is a pure frontend project with all data stored locally in your browser and never uploaded to any server, making the online version both safe and reliable to use.
-### 2. Vercel部署
-方式1:一键部署到自己的Vercel(方便,但后续无法自动更新):
- [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flinshenkx%2Fprompt-optimizer)
+### 2. Vercel Deployment
+Method 1: One-click deployment to your own Vercel:
+ [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flinshenkx%2Fprompt-optimizer)
-方式2: Fork项目后在Vercel中导入(推荐,但需参考部署文档进行手动设置):
- - 先Fork项目到自己的GitHub
- - 然后在Vercel中导入该项目
- - 可跟踪源项目更新,便于同步最新功能和修复
-- 配置环境变量:
- - `ACCESS_PASSWORD`:设置访问密码,启用访问限制
- - `VITE_OPENAI_API_KEY`等:配置各AI服务商的API密钥
+Method 2: Fork the project and import to Vercel (Recommended):
+ - First fork the project to your GitHub account
+ - Then import the project to Vercel
+ - This allows tracking of source project updates for easy syncing of new features and fixes
+- Configure environment variables:
+ - `ACCESS_PASSWORD`: Set access password to enable access restriction
+ - `VITE_OPENAI_API_KEY` etc.: Configure API keys for various AI service providers
+
+For more detailed deployment steps and important notes, please check:
+- [Vercel Deployment Guide](docs/user/deployment/vercel_en.md)
-更多详细的部署步骤和注意事项,请查看:
-- [Vercel部署指南](docs/user/deployment/vercel.md)
+### 3. Download Desktop Application
+Download the latest version from [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases). We provide both **installer** and **archive** formats for each platform.
-### 3. 下载桌面应用
-从 [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases) 下载最新版本。我们为各平台提供**安装程序**和**压缩包**两种格式。
+- **Installer (Recommended)**: Such as `*.exe`, `*.dmg`, `*.AppImage`, etc. **Strongly recommended as it supports automatic updates**.
+- **Archive**: Such as `*.zip`. Extract and use, but cannot auto-update.
-- **安装程序 (推荐)**: 如 `*.exe`, `*.dmg`, `*.AppImage` 等。**强烈推荐使用此方式,因为它支持自动更新**。
-- **压缩包**: 如 `*.zip`。解压即用,但无法自动更新。
+**Core Advantages of Desktop Application**:
+- ✅ **No CORS Limitations**: As a native desktop application, it completely eliminates browser Cross-Origin Resource Sharing (CORS) issues. This means you can directly connect to any AI service provider's API, including locally deployed Ollama or commercial APIs with strict security policies, for the most complete and stable functional experience.
+- ✅ **Automatic Updates**: Versions installed through installers (like `.exe`, `.dmg`) can automatically check and update to the latest version.
+- ✅ **Independent Operation**: No browser dependency, providing faster response and better performance.
-**桌面应用核心优势**:
-- ✅ **无跨域限制**:作为原生桌面应用,它能彻底摆脱浏览器跨域(CORS)问题的困扰。这意味着您可以直接连接任何AI服务提供商的API,包括本地部署的Ollama或有严格安全策略的商业API,获得最完整、最稳定的功能体验。
-- ✅ **自动更新**:通过安装程序(如 `.exe`, `.dmg`)安装的版本,能够自动检查并更新到最新版。
-- ✅ **独立运行**:无需依赖浏览器,提供更快的响应和更佳的性能。
+### 4. Install Chrome Extension
+1. Install from Chrome Web Store (may not be the latest version due to approval delays): [Chrome Web Store](https://chromewebstore.google.com/detail/prompt-optimizer/cakkkhboolfnadechdlgdcnjammejlna)
+2. Click the icon to open the Prompt Optimizer
-### 4. 安装Chrome插件
-1. 从Chrome商店安装(由于审批较慢,可能不是最新的):[Chrome商店地址](https://chromewebstore.google.com/detail/prompt-optimizer/cakkkhboolfnadechdlgdcnjammejlna)
-2. 点击图标即可打开提示词优化器
-
-### 5. Docker部署
+### 5. Docker Deployment
-点击查看 Docker 部署命令
-
+Click to view Docker deployment commands
```bash
-# 运行容器(默认配置)
+# Run container (default configuration)
docker run -d -p 8081:80 --restart unless-stopped --name prompt-optimizer linshen/prompt-optimizer
-# 运行容器(配置API密钥和访问密码)
+# Run container (with API key configuration and password protection)
docker run -d -p 8081:80 \
-e VITE_OPENAI_API_KEY=your_key \
- -e ACCESS_USERNAME=your_username \ # 可选,默认为"admin"
- -e ACCESS_PASSWORD=your_password \ # 设置访问密码
+ -e ACCESS_USERNAME=your_username \ # Optional, defaults to "admin"
+ -e ACCESS_PASSWORD=your_password \ # Set access password
--restart unless-stopped \
--name prompt-optimizer \
linshen/prompt-optimizer
```
-> **国内镜像**: 如果Docker Hub访问较慢,可以将上述命令中的 `linshen/prompt-optimizer` 替换为 `registry.cn-guangzhou.aliyuncs.com/prompt-optimizer/prompt-optimizer`
-
-### 6. Docker Compose部署
+### 6. Docker Compose Deployment
-点击查看 Docker Compose 部署步骤
-
+Click to view Docker Compose deployment steps
```bash
-# 1. 克隆仓库
+# 1. Clone the repository
git clone https://github.com/linshenkx/prompt-optimizer.git
cd prompt-optimizer
-# 2. 可选:创建.env文件配置API密钥和访问认证
-cp env.local.example .env
-# 编辑 .env 文件,填入实际的 API 密钥和配置
+# 2. Optional: Create .env file for API keys and authentication
+cat > .env << EOF
+# API Key Configuration
+VITE_OPENAI_API_KEY=your_openai_api_key
+VITE_GEMINI_API_KEY=your_gemini_api_key
+VITE_DEEPSEEK_API_KEY=your_deepseek_api_key
+VITE_ZHIPU_API_KEY=your_zhipu_api_key
+VITE_SILICONFLOW_API_KEY=your_siliconflow_api_key
-# 3. 启动服务
+# Basic Authentication (Password Protection)
+ACCESS_USERNAME=your_username # Optional, defaults to "admin"
+ACCESS_PASSWORD=your_password # Set access password
+EOF
+
+# 3. Start the service
docker compose up -d
-# 4. 查看日志
+# 4. View logs
docker compose logs -f
-# 5. 访问服务
-Web 界面:http://localhost:8081
-MCP 服务器:http://localhost:8081/mcp
+# 5. Access the service
+Web Interface: http://localhost:8081
+MCP Server: http://localhost:8081/mcp
```
-你还可以直接编辑docker-compose.yml文件,自定义配置:
+You can also directly edit the docker-compose.yml file to customize your configuration:
-点击查看 docker-compose.yml 示例
+Click to view docker-compose.yml example
```yaml
services:
prompt-optimizer:
- # 使用Docker Hub镜像
+ # Use Docker Hub image
image: linshen/prompt-optimizer:latest
- # 或使用阿里云镜像(国内用户推荐)
- # image: registry.cn-guangzhou.aliyuncs.com/prompt-optimizer/prompt-optimizer:latest
container_name: prompt-optimizer
restart: unless-stopped
ports:
- - "8081:80" # Web应用端口(包含MCP服务器,通过/mcp路径访问)
+ - "8081:80" # Web application port (MCP server accessible via /mcp path)
environment:
- # API密钥配置
- VITE_OPENAI_API_KEY=your_openai_key
- VITE_GEMINI_API_KEY=your_gemini_key
- # 访问控制(可选)
+ # Access Control (Optional)
- ACCESS_USERNAME=admin
- ACCESS_PASSWORD=your_password
```
-### 7. MCP Server 使用说明
+### 7. MCP Server Usage Instructions
-点击查看 MCP Server 使用说明
+Click to view MCP Server usage instructions
-Prompt Optimizer 现在支持 Model Context Protocol (MCP) 协议,可以与 Claude Desktop 等支持 MCP 的 AI 应用集成。
+Prompt Optimizer now supports the Model Context Protocol (MCP), enabling integration with AI applications that support MCP such as Claude Desktop.
-当通过 Docker 运行时,MCP Server 会自动启动,并可通过 `http://ip:port/mcp` 访问。
+When running via Docker, the MCP Server automatically starts and can be accessed via `http://ip:port/mcp`.
-#### 环境变量配置
+#### Environment Variable Configuration
-MCP Server 需要配置 API 密钥才能正常工作。主要的 MCP 专属配置:
+MCP Server requires API key configuration to function properly. Main MCP-specific configurations:
```bash
-# MCP 服务器配置
-MCP_DEFAULT_MODEL_PROVIDER=openai # 可选值:openai, gemini, anthropic, deepseek, siliconflow, zhipu, dashscope, openrouter, modelscope, custom
-MCP_LOG_LEVEL=info # 日志级别
+# MCP Server Configuration
+MCP_DEFAULT_MODEL_PROVIDER=openai # Options: openai, gemini, anthropic, deepseek, siliconflow, zhipu, dashscope, openrouter, modelscope, custom
+MCP_LOG_LEVEL=info # Log level
```
-#### Docker 环境下使用 MCP
+#### Using MCP in Docker Environment
-在 Docker 环境中,MCP Server 会与 Web 应用一起运行,您可以通过 Web 应用的相同端口访问 MCP 服务,路径为 `/mcp`。
+In a Docker environment, the MCP Server runs alongside the web application. You can access the MCP service through the same port as the web application at the `/mcp` path.
-例如,如果您将容器的 80 端口映射到主机的 8081 端口:
+For example, if you map the container's port 80 to port 8081 on the host:
```bash
docker run -d -p 8081:80 \
-e VITE_OPENAI_API_KEY=your-openai-key \
@@ -210,18 +213,18 @@ docker run -d -p 8081:80 \
linshen/prompt-optimizer
```
-那么 MCP Server 将可以通过 `http://localhost:8081/mcp` 访问。
+The MCP Server will then be accessible at `http://localhost:8081/mcp`.
-#### Claude Desktop 集成示例
+#### Claude Desktop Integration Example
-要在 Claude Desktop 中使用 Prompt Optimizer,您需要在 Claude Desktop 的配置文件中添加服务配置。
+To use Prompt Optimizer in Claude Desktop, you need to add the service configuration to Claude Desktop's configuration file.
-1. 找到 Claude Desktop 的配置目录:
+1. Find Claude Desktop's configuration directory:
- Windows: `%APPDATA%\Claude\services`
- macOS: `~/Library/Application Support/Claude/services`
- Linux: `~/.config/Claude/services`
-2. 编辑或创建 `services.json` 文件,添加以下内容:
+2. Edit or create the `services.json` file, adding the following content:
```json
{
@@ -234,42 +237,42 @@ docker run -d -p 8081:80 \
}
```
-请确保将 `localhost:8081` 替换为您实际部署 Prompt Optimizer 的地址和端口。
+Make sure to replace `localhost:8081` with the actual address and port where you've deployed Prompt Optimizer.
-#### 可用工具
+#### Available Tools
-- **optimize-user-prompt**: 优化用户提示词以提高 LLM 性能
-- **optimize-system-prompt**: 优化系统提示词以提高 LLM 性能
-- **iterate-prompt**: 对已经成熟/完善的提示词进行定向迭代优化
+- **optimize-user-prompt**: Optimize user prompts to improve LLM performance
+- **optimize-system-prompt**: Optimize system prompts to improve LLM performance
+- **iterate-prompt**: Iteratively improve mature prompts based on specific requirements
-更多详细信息,请查看 [MCP 服务器用户指南](docs/user/mcp-server.md)。
+For more detailed information, please refer to the [MCP Server User Guide](docs/user/mcp-server_en.md).
-## ⚙️ API密钥配置
+## ⚙️ API Key Configuration
-点击查看API密钥配置方法
+Click to view API key configuration methods
-### 方式一:通过界面配置(推荐)
-1. 点击界面右上角的"⚙️设置"按钮
-2. 选择"模型管理"选项卡
-3. 点击需要配置的模型(如OpenAI、Gemini、DeepSeek等)
-4. 在弹出的配置框中输入对应的API密钥
-5. 点击"保存"即可
+### Method 1: Via Interface (Recommended)
+1. Click the "⚙️Settings" button in the upper right corner
+2. Select the "Model Management" tab
+3. Click on the model you need to configure (such as OpenAI, Gemini, DeepSeek, etc.)
+4. Enter the corresponding API key in the configuration box
+5. Click "Save"
-支持的模型:OpenAI、Gemini、DeepSeek、Zhipu智谱、SiliconFlow、自定义API(OpenAI兼容接口)
+Supported models: OpenAI, Gemini, DeepSeek, Zhipu AI, SiliconFlow, Custom API (OpenAI compatible interface)
-除了API密钥,您还可以在模型配置界面为每个模型单独设置高级LLM参数。这些参数通过一个名为 `llmParams` 的字段进行配置,它允许您以键值对的形式指定LLM SDK支持的任何参数,从而更精细地控制模型行为。
+In addition to API keys, you can configure advanced LLM parameters for each model individually. These parameters are configured through a field called `llmParams`, which allows you to specify any parameters supported by the LLM SDK in key-value pairs for fine-grained control over model behavior.
-**高级LLM参数配置示例:**
-- **OpenAI/兼容API**: `{"temperature": 0.7, "max_tokens": 4096, "timeout": 60000}`
+**Advanced LLM Parameter Configuration Examples:**
+- **OpenAI/Compatible APIs**: `{"temperature": 0.7, "max_tokens": 4096, "timeout": 60000}`
- **Gemini**: `{"temperature": 0.8, "maxOutputTokens": 2048, "topP": 0.95}`
- **DeepSeek**: `{"temperature": 0.5, "top_p": 0.9, "frequency_penalty": 0.1}`
-有关 `llmParams` 的更详细说明和配置指南,请参阅 [LLM参数配置指南](docs/developer/llm-params-guide.md)。
+For more detailed information about `llmParams` configuration, please refer to the [LLM Parameters Configuration Guide](docs/developer/llm-params-guide.md).
-### 方式二:通过环境变量配置
-Docker部署时通过 `-e` 参数配置环境变量:
+### Method 2: Via Environment Variables
+Configure environment variables through the `-e` parameter when deploying with Docker:
```bash
-e VITE_OPENAI_API_KEY=your_key
@@ -278,62 +281,62 @@ Docker部署时通过 `-e` 参数配置环境变量:
-e VITE_ZHIPU_API_KEY=your_key
-e VITE_SILICONFLOW_API_KEY=your_key
-# 多自定义模型配置(支持无限数量)
+# Multiple Custom Models Configuration (Unlimited Quantity)
-e VITE_CUSTOM_API_KEY_ollama=dummy_key
-e VITE_CUSTOM_API_BASE_URL_ollama=http://localhost:11434/v1
-e VITE_CUSTOM_API_MODEL_ollama=qwen2.5:7b
```
-> 📖 **详细配置指南**: 查看 [多自定义模型配置文档](./docs/user/multi-custom-models.md) 了解完整的配置方法和高级用法
+> 📖 **Detailed Configuration Guide**: See [Multiple Custom Models Documentation](./docs/user/multi-custom-models_en.md) for complete configuration methods and advanced usage
-## 本地开发
-详细文档可查看 [开发文档](dev.md)
+## Local Development
+For detailed documentation, see [Development Documentation](dev.md)
-点击查看本地开发命令
+Click to view local development commands
```bash
-# 1. 克隆项目
+# 1. Clone the project
git clone https://github.com/linshenkx/prompt-optimizer.git
cd prompt-optimizer
-# 2. 安装依赖
+# 2. Install dependencies
pnpm install
-# 3. 启动开发服务
-pnpm dev # 主开发命令:构建 core/ui 并运行 web 应用
-pnpm dev:fresh # 完整重置并重新启动开发环境
+# 3. Start development server
+pnpm dev # Main development command: build core/ui and run web app
+pnpm dev:web # Run web app only
+pnpm dev:fresh # Complete reset and restart development environment
```
-## 🗺️ 开发路线
+## 🗺️ Roadmap
-- [x] 基础功能开发
-- [x] Web应用发布
-- [x] Chrome插件发布
-- [x] 国际化支持
-- [x] 支持系统提示词优化和用户提示词优化
-- [x] 桌面应用发布
-- [x] MCP服务发布
-- [x] 高级模式:变量管理、上下文测试、工具调用
-- [x] 图像生成:文生图(T2I)和图生图(I2I)支持
-- [x] 提示词收藏和模板管理
-- [ ] 支持工作区/项目管理
+- [x] Basic feature development
+- [x] Web application release
+- [x] Chrome extension release
+- [x] Internationalization support
+- [x] Support for system prompt optimization and user prompt optimization
+- [x] Desktop application release
+- [x] MCP service release
+- [x] Advanced mode: Variable management, context testing, function calling
+- [x] Image generation: Text-to-Image (T2I) and Image-to-Image (I2I) support
+- [x] Prompt favorites and template management
+- [ ] Support for workspace/project management
-详细的项目状态可查看 [项目状态文档](docs/project/project-status.md)
+For detailed project status, see [Project Status Document](docs/project/project-status.md)
-## 📖 相关文档
-
-- [文档索引](docs/README.md) - 所有文档的索引
-- [技术开发指南](docs/developer/technical-development-guide.md) - 技术栈和开发规范
-- [LLM参数配置指南](docs/developer/llm-params-guide.md) - 高级LLM参数配置详细说明
-- [项目结构](docs/developer/project-structure.md) - 详细的项目结构说明
-- [项目状态](docs/project/project-status.md) - 当前进度和计划
-- [产品需求](docs/project/prd.md) - 产品需求文档
-- [Vercel部署指南](docs/user/deployment/vercel.md) - Vercel部署详细说明
+## 📖 Related Documentation
+- [Documentation Index](docs/README.md) - Index of all documentation
+- [Technical Development Guide](docs/developer/technical-development-guide.md) - Technology stack and development specifications
+- [LLM Parameters Configuration Guide](docs/developer/llm-params-guide.md) - Detailed guide for advanced LLM parameter configuration
+- [Project Structure](docs/developer/project-structure.md) - Detailed project structure description
+- [Project Status](docs/project/project-status.md) - Current progress and plans
+- [Product Requirements](docs/project/prd.md) - Product requirements document
+- [Vercel Deployment Guide](docs/user/deployment/vercel_en.md) - Detailed instructions for Vercel deployment
## Star History
@@ -345,131 +348,131 @@ pnpm dev:fresh # 完整重置并重新启动开发环境
-## 常见问题
+## FAQ
-点击查看常见问题解答
+Click to view frequently asked questions
-### API连接问题
+### API Connection Issues
-#### Q1: 为什么配置好API密钥后仍然无法连接到模型服务?
-**A**: 大多数连接失败是由**跨域问题**(CORS)导致的。由于本项目是纯前端应用,浏览器出于安全考虑会阻止直接访问不同源的API服务。模型服务如未正确配置CORS策略,会拒绝来自浏览器的直接请求。
+#### Q1: Why can't I connect to the model service after configuring the API key?
+**A**: Most connection failures are caused by **Cross-Origin Resource Sharing (CORS)** issues. As this project is a pure frontend application, browsers block direct access to API services from different origins for security reasons. Model services will reject direct requests from browsers if CORS policies are not correctly configured.
-#### Q2: 如何解决本地Ollama的连接问题?
-**A**: Ollama完全支持OpenAI标准接口,只需配置正确的跨域策略:
-1. 设置环境变量 `OLLAMA_ORIGINS=*` 允许任意来源的请求
-2. 如仍有问题,设置 `OLLAMA_HOST=0.0.0.0:11434` 监听任意IP地址
+#### Q2: How to solve Ollama connection issues?
+**A**: Ollama fully supports the OpenAI standard interface, just configure the correct CORS policy:
+1. Set environment variable `OLLAMA_ORIGINS=*` to allow requests from any origin
+2. If issues persist, set `OLLAMA_HOST=0.0.0.0:11434` to listen on any IP address
-#### Q3: 如何解决商业API(如Nvidia的DS API、字节跳动的火山API)的跨域问题?
-**A**: 这些平台通常有严格的跨域限制,推荐以下解决方案:
+#### Q3: How to solve CORS issues with commercial APIs (such as Nvidia's DS API, ByteDance's Volcano API)?
+**A**: These platforms typically have strict CORS restrictions. Recommended solutions:
-1. **使用桌面版应用**(最推荐)
- - 桌面应用作为原生应用,完全没有跨域限制
- - 可以直接连接任何API服务,包括本地部署的模型
- - 提供最完整、最稳定的功能体验
- - 从 [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases) 下载
+1. **Use Desktop Application** (Most Recommended)
+ - Desktop app has no CORS restrictions as a native application
+ - Can directly connect to any API service, including locally deployed models
+ - Provides the most complete and stable feature experience
+ - Download from [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases)
-2. **使用自部署的API中转服务**(专业方案)
- - 部署如OneAPI、NewAPI等开源API聚合/代理工具
- - 在设置中配置为自定义API端点
- - 请求流向:浏览器→中转服务→模型服务提供商
- - 完全控制安全策略和访问权限
+2. **Use Self-deployed API Proxy Service** (Professional solution)
+ - Deploy open-source API aggregation/proxy tools like OneAPI, NewAPI
+ - Configure as custom API endpoint in settings
+ - Request flow: Browser → Proxy service → Model service provider
+ - Full control over security policies and access permissions
-**注意**:Web版(包括在线版、Vercel部署、Docker部署)都是纯前端应用,都会受到浏览器CORS限制。只有桌面版或使用API中转服务才能解决跨域问题。
+**Note**: All web versions (including online version, Vercel deployment, Docker deployment) are pure frontend applications and subject to browser CORS restrictions. Only the desktop version or using an API proxy service can solve CORS issues.
-#### Q4: 我已正确配置本地模型(如Ollama)的跨域策略,为什么使用在线版依然无法连接?
-**A**: 这是由浏览器的**混合内容(Mixed Content)安全策略**导致的。出于安全考虑,浏览器会阻止安全的HTTPS页面(如在线版)向不安全的HTTP地址(如您的本地Ollama服务)发送请求。
+#### Q4: I have correctly configured CORS policies for my local model (like Ollama), why can't I still connect using the online version?
+**A**: This is caused by the browser's **Mixed Content security policy**. For security reasons, browsers block secure HTTPS pages (like the online version) from sending requests to insecure HTTP addresses (like your local Ollama service).
-**解决方案**:
-为了绕过此限制,您需要让应用和API处于同一种协议下(例如,都是HTTP)。推荐以下方式:
-1. **使用桌面版**:桌面应用没有浏览器限制,是连接本地模型最稳定可靠的方式
-2. **使用Docker部署(HTTP)**:通过 `http://localhost:8081` 访问,与本地Ollama都是HTTP
-3. **使用Chrome插件**:插件在某些情况下也可以绕过部分安全限制
+**Solutions**:
+To bypass this limitation, you need to have the application and API under the same protocol (e.g., both HTTP). We recommend the following approaches:
+1. **Use the desktop version**: Desktop applications have no browser restrictions and are the most stable and reliable way to connect to local models
+2. **Use Docker deployment (HTTP)**: Access via `http://localhost:8081`, both the app and local Ollama use HTTP
+3. **Use Chrome extension**: Extensions can bypass some security restrictions in certain situations
-### macOS 桌面应用问题
+### macOS Desktop Application Issues
-#### Q5: macOS 打开应用时提示「已损坏」或「无法验证开发者」怎么办?
-**A**: 这是因为应用未经过 Apple 签名认证。由于 Apple 开发者账号费用较高,目前桌面应用暂未进行签名。
+#### Q5: macOS shows "damaged" or "unverified developer" when opening the app?
+**A**: This is because the application has not been signed with an Apple Developer certificate. Due to the high cost of Apple Developer accounts, the desktop application is currently unsigned.
-**解决方案**:
-在终端中执行以下命令移除安全隔离属性:
+**Solution**:
+Run the following command in Terminal to remove the quarantine attribute:
```bash
-# 对于已安装的应用
+# For installed applications
xattr -rd com.apple.quarantine /Applications/PromptOptimizer.app
-# 对于下载的 .dmg 文件(安装前执行)
+# For downloaded .dmg files (run before installation)
xattr -rd com.apple.quarantine ~/Downloads/PromptOptimizer-*.dmg
```
-执行后重新打开应用即可正常使用。
+After running the command, you can open the application normally.
-## 🤝 参与贡献
+## 🤝 Contributing
-点击查看贡献指南
+Click to view contribution guidelines
-1. Fork 本仓库
-2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
-3. 提交更改 (`git commit -m '添加某个特性'`)
-4. 推送到分支 (`git push origin feature/AmazingFeature`)
-5. 提交 Pull Request
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add some feature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
-提示:使用cursor工具开发时,建议在提交前:
-1. 使用"code_review"规则进行代码审查
-2. 按照审查报告格式检查:
- - 变更的整体一致性
- - 代码质量和实现方式
- - 测试覆盖情况
- - 文档完善程度
-3. 根据审查结果进行优化后再提交
+Tip: When developing with Cursor tool, it is recommended to do the following before committing:
+1. Use the "CodeReview" rule for review
+2. Check according to the review report format:
+ - Overall consistency of changes
+ - Code quality and implementation method
+ - Test coverage
+ - Documentation completeness
+3. Optimize based on review results before submitting
-## 👏 贡献者名单
+## 👏 Contributors
-感谢所有为项目做出贡献的开发者!
+Thanks to all the developers who have contributed to this project!
-
+
-## 🙏 鸣谢
+## 🙏 Acknowledgements
-本项目在提示词工程与结构化提示词设计的探索中,受到了 [LangGPT](https://github.com/langgptai/LangGPT) 的启发。感谢 LangGPT 项目及其社区的开源分享与持续探索。
+This project was partly inspired by [LangGPT](https://github.com/langgptai/LangGPT) in prompt engineering and structured prompt design. Thanks to the LangGPT project and community for their open-source sharing and continued exploration.
-## 📄 开源协议
+## 📄 License
-本项目采用 [AGPL-3.0](LICENSE) 协议开源。
+This project is licensed under [AGPL-3.0](LICENSE).
-**简单来说**:你可以自由使用、修改和商用本项目,但如果你把它做成网站或服务给别人用,需要公开你的源代码。
+**In simple terms**: You can freely use, modify, and commercialize this project, but if you turn it into a website or service for others, you must share your source code.
-👉 点击查看详细说明
+👉 Click for detailed explanation
-**允许做什么:**
-- ✅ 个人使用、学习、研究
-- ✅ 公司内部使用(不对外提供服务)
-- ✅ 修改代码并用于商业项目
-- ✅ 收费销售或提供服务
+**What you can do:**
+- ✅ Personal use, learning, and research
+- ✅ Internal company use (not offering public services)
+- ✅ Modify code for commercial projects
+- ✅ Charge for products or services
-**需要做什么:**
-- 📖 如果分发软件或提供网络服务,必须公开源代码
-- 📝 保留原作者的版权声明
+**What you must do:**
+- 📖 If distributing software or offering network services, disclose source code
+- 📝 Preserve original author's copyright notices
-**一句话核心**:可以商用,但不能闭源。
+**Core principle**: Commercial use is allowed, but not closed-source.
---
-如果这个项目对你有帮助,请考虑给它一个 Star ⭐️
+If this project is helpful to you, please consider giving it a Star ⭐️
-## 👥 联系我们
+## 👥 Contact Us
-- 提交 Issue
-- 发起 Pull Request
-- 加入讨论组
+- Submit an Issue
+- Create a Pull Request
+- Join the discussion group
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 00000000..160ac3ff
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,475 @@
+# Prompt Optimizer (提示词优化器) 🚀
+
+
-
-## 📖 Project Introduction
-
-Prompt Optimizer is a powerful AI prompt optimization tool that helps you write better AI prompts and improve the quality of AI outputs. It supports four usage methods: web application, desktop application, Chrome extension, and Docker deployment.
-
-### 🎥 Feature Demonstration
-
-
-
1. Hard-Nosed Reviewer: Turn Agreement into Useful Critique
-
Starting from a minimal English role prompt, optimization pushes a small model away from generic pushback and toward a clearer, more structured review that surfaces weak assumptions, evidence gaps, and concrete revision advice.
-
-
-
2. Marketplace Bargaining Reply: Let Variables Change the Strategy
-
With a single reusable prompt template, you can swap in item details, price anchors, buyer offers, tone, and negotiation goals for different marketplace conversations. After optimization, the same small model does a better job turning those variables into a clearer, more transaction-ready reply instead of a generic helper-style response.
-
-
-
3. Text-to-Image: Optimize a One-Line Idea into a More Directable Key Visual Prompt
-
This is not just prompt expansion. Starting from a vague one-line idea, Prompt Optimizer adds clearer subject cues, spatial relationships, and mood anchors. The left side is simply “a floating library in the night sky,” while the optimized version gives the model a more directed fantasy composition that feels closer to a reusable key visual than a lucky generic image.
-
-
-
-## ✨ Core Features
-
-- 🎯 **Intelligent Optimization**: One-click prompt optimization with multi-round iterative improvements to enhance AI response accuracy
-- 📝 **Dual Mode Optimization**: Support for both system prompt optimization and user prompt optimization to meet different usage scenarios
-- 🔄 **Analysis and Compare Evaluation**: Supports analysis, single-result evaluation, and multi-result compare evaluation to help determine whether a prompt has truly improved
-- 🤖 **Multi-model Integration**: Support for mainstream AI models including OpenAI, Gemini, DeepSeek, Zhipu AI, SiliconFlow, etc.
-- 🖼️ **Image Generation**: Support for Text-to-Image (T2I) and Image-to-Image (I2I) with models like Gemini, Seedream
-- 📊 **Advanced Testing Mode**: Context variable management, multi-turn conversation testing, Function Calling support
-- 🔒 **Secure Architecture**: Pure client-side processing with direct data interaction with AI service providers, bypassing intermediate servers
-- 📱 **Multi-platform Support**: Available as web application, desktop application, Chrome extension, and Docker deployment
-- 🔐 **Access Control**: Password protection feature for secure deployment
-- 🧩 **MCP Protocol Support**: Supports Model Context Protocol (MCP), enabling integration with MCP-compatible AI applications like Claude Desktop
-
-## 🚀 Advanced Features
-
-### Image Generation Mode
-- 🖼️ **Text-to-Image (T2I)**: Generate images from text prompts
-- 🎨 **Image-to-Image (I2I)**: Transform and optimize images based on local files
-- 🔌 **Multi-model Support**: Integrated with mainstream image generation models like Gemini, Seedream
-- ⚙️ **Model Parameters**: Support model-specific parameter configuration (size, style, etc.)
-- 📥 **Preview & Download**: Real-time preview of generated results with download support
-
-### Advanced Testing Mode
-- 📊 **Context Variable Management**: Custom variables, batch replacement, variable preview
-- 💬 **Multi-turn Conversation Testing**: Simulate real conversation scenarios to test prompt performance in multi-turn interactions
-- 🛠️ **Function Calling Support**: Function Calling integration with support for OpenAI and Gemini tool calling
-- 🔍 **Analysis and Evaluation Pipeline**: Supports analysis, evaluation, compare evaluation, and evaluation-driven smart rewrite in text modes
-
-For detailed usage instructions, please refer to the [Image Mode Documentation](docs/image-mode.md)
-
-## Quick Start
-
-### 1. Use Online Version (Recommended)
-
-Direct access: [https://prompt.always200.com](https://prompt.always200.com)
-
-This is a pure frontend project with all data stored locally in your browser and never uploaded to any server, making the online version both safe and reliable to use.
-
-### 2. Vercel Deployment
-Method 1: One-click deployment to your own Vercel:
- [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flinshenkx%2Fprompt-optimizer)
-
-Method 2: Fork the project and import to Vercel (Recommended):
- - First fork the project to your GitHub account
- - Then import the project to Vercel
- - This allows tracking of source project updates for easy syncing of new features and fixes
-- Configure environment variables:
- - `ACCESS_PASSWORD`: Set access password to enable access restriction
- - `VITE_OPENAI_API_KEY` etc.: Configure API keys for various AI service providers
-
-For more detailed deployment steps and important notes, please check:
-- [Vercel Deployment Guide](docs/user/deployment/vercel_en.md)
-
-### 3. Download Desktop Application
-Download the latest version from [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases). We provide both **installer** and **archive** formats for each platform.
-
-- **Installer (Recommended)**: Such as `*.exe`, `*.dmg`, `*.AppImage`, etc. **Strongly recommended as it supports automatic updates**.
-- **Archive**: Such as `*.zip`. Extract and use, but cannot auto-update.
-
-**Core Advantages of Desktop Application**:
-- ✅ **No CORS Limitations**: As a native desktop application, it completely eliminates browser Cross-Origin Resource Sharing (CORS) issues. This means you can directly connect to any AI service provider's API, including locally deployed Ollama or commercial APIs with strict security policies, for the most complete and stable functional experience.
-- ✅ **Automatic Updates**: Versions installed through installers (like `.exe`, `.dmg`) can automatically check and update to the latest version.
-- ✅ **Independent Operation**: No browser dependency, providing faster response and better performance.
-
-### 4. Install Chrome Extension
-1. Install from Chrome Web Store (may not be the latest version due to approval delays): [Chrome Web Store](https://chromewebstore.google.com/detail/prompt-optimizer/cakkkhboolfnadechdlgdcnjammejlna)
-2. Click the icon to open the Prompt Optimizer
-
-### 5. Docker Deployment
-
-Click to view Docker deployment commands
-```bash
-# Run container (default configuration)
-docker run -d -p 8081:80 --restart unless-stopped --name prompt-optimizer linshen/prompt-optimizer
-
-# Run container (with API key configuration and password protection)
-docker run -d -p 8081:80 \
- -e VITE_OPENAI_API_KEY=your_key \
- -e ACCESS_USERNAME=your_username \ # Optional, defaults to "admin"
- -e ACCESS_PASSWORD=your_password \ # Set access password
- --restart unless-stopped \
- --name prompt-optimizer \
- linshen/prompt-optimizer
-```
-
-
-### 6. Docker Compose Deployment
-
-Click to view Docker Compose deployment steps
-```bash
-# 1. Clone the repository
-git clone https://github.com/linshenkx/prompt-optimizer.git
-cd prompt-optimizer
-
-# 2. Optional: Create .env file for API keys and authentication
-cat > .env << EOF
-# API Key Configuration
-VITE_OPENAI_API_KEY=your_openai_api_key
-VITE_GEMINI_API_KEY=your_gemini_api_key
-VITE_DEEPSEEK_API_KEY=your_deepseek_api_key
-VITE_ZHIPU_API_KEY=your_zhipu_api_key
-VITE_SILICONFLOW_API_KEY=your_siliconflow_api_key
-
-# Basic Authentication (Password Protection)
-ACCESS_USERNAME=your_username # Optional, defaults to "admin"
-ACCESS_PASSWORD=your_password # Set access password
-EOF
-
-# 3. Start the service
-docker compose up -d
-
-# 4. View logs
-docker compose logs -f
-
-# 5. Access the service
-Web Interface: http://localhost:8081
-MCP Server: http://localhost:8081/mcp
-```
-
-
-You can also directly edit the docker-compose.yml file to customize your configuration:
-
-Click to view docker-compose.yml example
-
-```yaml
-services:
- prompt-optimizer:
- # Use Docker Hub image
- image: linshen/prompt-optimizer:latest
- container_name: prompt-optimizer
- restart: unless-stopped
- ports:
- - "8081:80" # Web application port (MCP server accessible via /mcp path)
- environment:
- - VITE_OPENAI_API_KEY=your_openai_key
- - VITE_GEMINI_API_KEY=your_gemini_key
- # Access Control (Optional)
- - ACCESS_USERNAME=admin
- - ACCESS_PASSWORD=your_password
-```
-
-
-### 7. MCP Server Usage Instructions
-
-Click to view MCP Server usage instructions
-
-Prompt Optimizer now supports the Model Context Protocol (MCP), enabling integration with AI applications that support MCP such as Claude Desktop.
-
-When running via Docker, the MCP Server automatically starts and can be accessed via `http://ip:port/mcp`.
-
-#### Environment Variable Configuration
-
-MCP Server requires API key configuration to function properly. Main MCP-specific configurations:
-
-```bash
-# MCP Server Configuration
-MCP_DEFAULT_MODEL_PROVIDER=openai # Options: openai, gemini, anthropic, deepseek, siliconflow, zhipu, dashscope, openrouter, modelscope, custom
-MCP_LOG_LEVEL=info # Log level
-```
-
-#### Using MCP in Docker Environment
-
-In a Docker environment, the MCP Server runs alongside the web application. You can access the MCP service through the same port as the web application at the `/mcp` path.
-
-For example, if you map the container's port 80 to port 8081 on the host:
-```bash
-docker run -d -p 8081:80 \
- -e VITE_OPENAI_API_KEY=your-openai-key \
- -e MCP_DEFAULT_MODEL_PROVIDER=openai \
- --name prompt-optimizer \
- linshen/prompt-optimizer
-```
-
-The MCP Server will then be accessible at `http://localhost:8081/mcp`.
-
-#### Claude Desktop Integration Example
-
-To use Prompt Optimizer in Claude Desktop, you need to add the service configuration to Claude Desktop's configuration file.
-
-1. Find Claude Desktop's configuration directory:
- - Windows: `%APPDATA%\Claude\services`
- - macOS: `~/Library/Application Support/Claude/services`
- - Linux: `~/.config/Claude/services`
-
-2. Edit or create the `services.json` file, adding the following content:
-
-```json
-{
- "services": [
- {
- "name": "Prompt Optimizer",
- "url": "http://localhost:8081/mcp"
- }
- ]
-}
-```
-
-Make sure to replace `localhost:8081` with the actual address and port where you've deployed Prompt Optimizer.
-
-#### Available Tools
-
-- **optimize-user-prompt**: Optimize user prompts to improve LLM performance
-- **optimize-system-prompt**: Optimize system prompts to improve LLM performance
-- **iterate-prompt**: Iteratively improve mature prompts based on specific requirements
-
-For more detailed information, please refer to the [MCP Server User Guide](docs/user/mcp-server_en.md).
-
-
-## ⚙️ API Key Configuration
-
-
-Click to view API key configuration methods
-
-### Method 1: Via Interface (Recommended)
-1. Click the "⚙️Settings" button in the upper right corner
-2. Select the "Model Management" tab
-3. Click on the model you need to configure (such as OpenAI, Gemini, DeepSeek, etc.)
-4. Enter the corresponding API key in the configuration box
-5. Click "Save"
-
-Supported models: OpenAI, Gemini, DeepSeek, Zhipu AI, SiliconFlow, Custom API (OpenAI compatible interface)
-
-In addition to API keys, you can configure advanced LLM parameters for each model individually. These parameters are configured through a field called `llmParams`, which allows you to specify any parameters supported by the LLM SDK in key-value pairs for fine-grained control over model behavior.
-
-**Advanced LLM Parameter Configuration Examples:**
-- **OpenAI/Compatible APIs**: `{"temperature": 0.7, "max_tokens": 4096, "timeout": 60000}`
-- **Gemini**: `{"temperature": 0.8, "maxOutputTokens": 2048, "topP": 0.95}`
-- **DeepSeek**: `{"temperature": 0.5, "top_p": 0.9, "frequency_penalty": 0.1}`
-
-For more detailed information about `llmParams` configuration, please refer to the [LLM Parameters Configuration Guide](docs/developer/llm-params-guide.md).
-
-### Method 2: Via Environment Variables
-Configure environment variables through the `-e` parameter when deploying with Docker:
-
-```bash
--e VITE_OPENAI_API_KEY=your_key
--e VITE_GEMINI_API_KEY=your_key
--e VITE_DEEPSEEK_API_KEY=your_key
--e VITE_ZHIPU_API_KEY=your_key
--e VITE_SILICONFLOW_API_KEY=your_key
-
-# Multiple Custom Models Configuration (Unlimited Quantity)
--e VITE_CUSTOM_API_KEY_ollama=dummy_key
--e VITE_CUSTOM_API_BASE_URL_ollama=http://localhost:11434/v1
--e VITE_CUSTOM_API_MODEL_ollama=qwen2.5:7b
-```
-
-> 📖 **Detailed Configuration Guide**: See [Multiple Custom Models Documentation](./docs/user/multi-custom-models_en.md) for complete configuration methods and advanced usage
-
-
-
-## Local Development
-For detailed documentation, see [Development Documentation](dev.md)
-
-
-Click to view local development commands
-
-```bash
-# 1. Clone the project
-git clone https://github.com/linshenkx/prompt-optimizer.git
-cd prompt-optimizer
-
-# 2. Install dependencies
-pnpm install
-
-# 3. Start development server
-pnpm dev # Main development command: build core/ui and run web app
-pnpm dev:web # Run web app only
-pnpm dev:fresh # Complete reset and restart development environment
-```
-
-
-## 🗺️ Roadmap
-
-- [x] Basic feature development
-- [x] Web application release
-- [x] Chrome extension release
-- [x] Internationalization support
-- [x] Support for system prompt optimization and user prompt optimization
-- [x] Desktop application release
-- [x] MCP service release
-- [x] Advanced mode: Variable management, context testing, function calling
-- [x] Image generation: Text-to-Image (T2I) and Image-to-Image (I2I) support
-- [x] Prompt favorites and template management
-- [ ] Support for workspace/project management
-
-For detailed project status, see [Project Status Document](docs/project/project-status.md)
-
-## 📖 Related Documentation
-
-- [Documentation Index](docs/README.md) - Index of all documentation
-- [Technical Development Guide](docs/developer/technical-development-guide.md) - Technology stack and development specifications
-- [LLM Parameters Configuration Guide](docs/developer/llm-params-guide.md) - Detailed guide for advanced LLM parameter configuration
-- [Project Structure](docs/developer/project-structure.md) - Detailed project structure description
-- [Project Status](docs/project/project-status.md) - Current progress and plans
-- [Product Requirements](docs/project/prd.md) - Product requirements document
-- [Vercel Deployment Guide](docs/user/deployment/vercel_en.md) - Detailed instructions for Vercel deployment
-
-## Star History
-
-
-
-
-
-
-
-
-
-## FAQ
-
-
-Click to view frequently asked questions
-
-### API Connection Issues
-
-#### Q1: Why can't I connect to the model service after configuring the API key?
-**A**: Most connection failures are caused by **Cross-Origin Resource Sharing (CORS)** issues. As this project is a pure frontend application, browsers block direct access to API services from different origins for security reasons. Model services will reject direct requests from browsers if CORS policies are not correctly configured.
-
-#### Q2: How to solve Ollama connection issues?
-**A**: Ollama fully supports the OpenAI standard interface, just configure the correct CORS policy:
-1. Set environment variable `OLLAMA_ORIGINS=*` to allow requests from any origin
-2. If issues persist, set `OLLAMA_HOST=0.0.0.0:11434` to listen on any IP address
-
-#### Q3: How to solve CORS issues with commercial APIs (such as Nvidia's DS API, ByteDance's Volcano API)?
-**A**: These platforms typically have strict CORS restrictions. Recommended solutions:
-
-1. **Use Desktop Application** (Most Recommended)
- - Desktop app has no CORS restrictions as a native application
- - Can directly connect to any API service, including locally deployed models
- - Provides the most complete and stable feature experience
- - Download from [GitHub Releases](https://github.com/linshenkx/prompt-optimizer/releases)
-
-2. **Use Self-deployed API Proxy Service** (Professional solution)
- - Deploy open-source API aggregation/proxy tools like OneAPI, NewAPI
- - Configure as custom API endpoint in settings
- - Request flow: Browser → Proxy service → Model service provider
- - Full control over security policies and access permissions
-
-**Note**: All web versions (including online version, Vercel deployment, Docker deployment) are pure frontend applications and subject to browser CORS restrictions. Only the desktop version or using an API proxy service can solve CORS issues.
-
-#### Q4: I have correctly configured CORS policies for my local model (like Ollama), why can't I still connect using the online version?
-**A**: This is caused by the browser's **Mixed Content security policy**. For security reasons, browsers block secure HTTPS pages (like the online version) from sending requests to insecure HTTP addresses (like your local Ollama service).
-
-**Solutions**:
-To bypass this limitation, you need to have the application and API under the same protocol (e.g., both HTTP). We recommend the following approaches:
-1. **Use the desktop version**: Desktop applications have no browser restrictions and are the most stable and reliable way to connect to local models
-2. **Use Docker deployment (HTTP)**: Access via `http://localhost:8081`, both the app and local Ollama use HTTP
-3. **Use Chrome extension**: Extensions can bypass some security restrictions in certain situations
-
-### macOS Desktop Application Issues
-
-#### Q5: macOS shows "damaged" or "unverified developer" when opening the app?
-**A**: This is because the application has not been signed with an Apple Developer certificate. Due to the high cost of Apple Developer accounts, the desktop application is currently unsigned.
-
-**Solution**:
-Run the following command in Terminal to remove the quarantine attribute:
-
-```bash
-# For installed applications
-xattr -rd com.apple.quarantine /Applications/PromptOptimizer.app
-
-# For downloaded .dmg files (run before installation)
-xattr -rd com.apple.quarantine ~/Downloads/PromptOptimizer-*.dmg
-```
-
-After running the command, you can open the application normally.
-
-
-
-
-## 🤝 Contributing
-
-
-Click to view contribution guidelines
-
-1. Fork the repository
-2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
-3. Commit your changes (`git commit -m 'Add some feature'`)
-4. Push to the branch (`git push origin feature/AmazingFeature`)
-5. Open a Pull Request
-
-Tip: When developing with Cursor tool, it is recommended to do the following before committing:
-1. Use the "CodeReview" rule for review
-2. Check according to the review report format:
- - Overall consistency of changes
- - Code quality and implementation method
- - Test coverage
- - Documentation completeness
-3. Optimize based on review results before submitting
-
-
-
-## 👏 Contributors
-
-Thanks to all the developers who have contributed to this project!
-
-
-
-
-
-## 🙏 Acknowledgements
-
-This project was partly inspired by [LangGPT](https://github.com/langgptai/LangGPT) in prompt engineering and structured prompt design. Thanks to the LangGPT project and community for their open-source sharing and continued exploration.
-
-## 📄 License
-
-This project is licensed under [AGPL-3.0](LICENSE).
-
-**In simple terms**: You can freely use, modify, and commercialize this project, but if you turn it into a website or service for others, you must share your source code.
-
-
-👉 Click for detailed explanation
-
-**What you can do:**
-- ✅ Personal use, learning, and research
-- ✅ Internal company use (not offering public services)
-- ✅ Modify code for commercial projects
-- ✅ Charge for products or services
-
-**What you must do:**
-- 📖 If distributing software or offering network services, disclose source code
-- 📝 Preserve original author's copyright notices
-
-**Core principle**: Commercial use is allowed, but not closed-source.
-
-
-
----
-
-If this project is helpful to you, please consider giving it a Star ⭐️
-
-## 👥 Contact Us
-
-- Submit an Issue
-- Create a Pull Request
-- Join the discussion group
diff --git a/docs/developer/i18n-policy.md b/docs/developer/i18n-policy.md
new file mode 100644
index 00000000..10d3b0d1
--- /dev/null
+++ b/docs/developer/i18n-policy.md
@@ -0,0 +1,43 @@
+# 国际化约定
+
+本仓库采用英文优先的国际化治理策略,目标是在不影响团队中文协作的前提下,让产品运行时文案、异常提示和默认入口保持英文主导并可持续维护。
+
+## 基本规则
+
+- 英文是 source locale,所有新增翻译键以 `en-US` 为基准。
+- 根 `README.md` 保持英文默认入口,中文说明放在 `README.zh-CN.md`。
+- 开发文档可以继续使用中文。
+- 注释允许保留中文,但新增或修改时优先写英文。
+
+## 运行时代码约束
+
+- 普通运行时代码中不允许新增中文用户可见文案。
+- UI 文案必须优先走 i18n key。
+- Core 层优先抛出结构化错误(`code + params`),不要把最终展示文案硬编码在业务逻辑里。
+- 非 UI 的内部错误和日志如果不做国际化,一律使用英文。
+
+## 允许保留中文的目录
+
+以下目录允许保留中文内容资产:
+
+- `docs/**`
+- `mkdocs/**`
+- `packages/ui/src/i18n/**`
+- `packages/core/src/services/template/default-templates/**`
+- `packages/ui/src/docs/**`
+- `packages/ui/src/examples/**`
+- 测试目录及其夹具
+
+如需新增允许目录,请同步更新中文扫描脚本中的 allowlist。
+
+## Locale 结构要求
+
+- locale 文件按领域拆分维护,不再使用单一超大文件直接编辑。
+- 所有语言包必须与 `en-US` 保持完全一致的 key 结构。
+- locale parity 校验和非法中文扫描属于 CI 阻断项。
+
+## 默认语言策略
+
+- 无用户偏好设置时,应用默认语言为 `en-US`。
+- 已保存偏好设置的用户保持原有行为。
+- 非法或未知 locale 一律回退到 `en-US`。
diff --git a/package.json b/package.json
index 3936d8cb..c87dff69 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"packageManager": "pnpm@10.6.1",
"engines": {
"node": "^22.0.0",
- "npm": "请使用pnpm代替npm",
- "yarn": "请使用pnpm代替yarn"
+ "npm": "Please use pnpm instead of npm",
+ "yarn": "Please use pnpm instead of yarn"
},
"scripts": {
"build": "node scripts/run-many.js build:core build:ui build:parallel",
@@ -28,6 +28,9 @@
"dev:desktop:parallel": "concurrently -k -p \"[{name}]\" -n \"WEB,DESKTOP\" \"pnpm -F @prompt-optimizer/web dev\" \"pnpm -F @prompt-optimizer/desktop dev\"",
"test": "pnpm test:unit",
"test:unit": "pnpm -r test --run --passWithNoTests",
+ "test:repo": "node --test scripts/run-many.test.mjs scripts/check-locale-parity.test.mjs scripts/check-no-chinese-runtime.test.mjs && pnpm check:locale && pnpm check:no-chinese-runtime",
+ "check:locale": "node scripts/check-locale-parity.mjs",
+ "check:no-chinese-runtime": "node scripts/check-no-chinese-runtime.mjs",
"test:e2e": "playwright test",
"test:e2e:smart": "node scripts/smart-e2e.js",
"test:e2e:gate": "node scripts/run-e2e-group.js gate",
@@ -38,7 +41,7 @@
"test:gate:core": "pnpm -F @prompt-optimizer/core test:gate",
"test:gate:ui": "pnpm -F @prompt-optimizer/core build && pnpm -F @prompt-optimizer/ui test",
"test:gate:e2e": "pnpm test:e2e:gate",
- "test:gate": "node scripts/run-many.js -s test:gate:core test:gate:ui",
+ "test:gate": "node scripts/run-many.js -s test:repo test:gate:core test:gate:ui",
"test:gate:full": "node scripts/run-many.js -s test:gate test:gate:e2e",
"test:fast": "pnpm -r test --run --passWithNoTests",
"test:e2e:ui": "playwright test --ui",
diff --git a/packages/core/src/services/adapters/abstract-registry.ts b/packages/core/src/services/adapters/abstract-registry.ts
index ceb588e2..444780ec 100644
--- a/packages/core/src/services/adapters/abstract-registry.ts
+++ b/packages/core/src/services/adapters/abstract-registry.ts
@@ -47,7 +47,7 @@ export abstract class AbstractAdapterRegistry<
* 子类可选实现:获取错误消息的提供商类型描述
*/
protected getProviderTypeDescription(): string {
- return '提供商';
+ return 'provider';
}
/**
@@ -196,7 +196,7 @@ export abstract class AbstractAdapterRegistry<
return mergedModels;
} catch (error) {
console.warn(
- `动态模型加载失败 (${providerId}),回退到静态模型:`,
+ `Failed to load dynamic models (${providerId}); falling back to static models:`,
error
);
// 降级到静态模型
diff --git a/packages/core/src/services/context/constants.ts b/packages/core/src/services/context/constants.ts
index aa246e60..40fdea27 100644
--- a/packages/core/src/services/context/constants.ts
+++ b/packages/core/src/services/context/constants.ts
@@ -27,7 +27,7 @@ export type PredefinedVariable = typeof PREDEFINED_VARIABLES[number];
// 默认上下文配置
export const DEFAULT_CONTEXT_CONFIG = {
id: 'default',
- title: '默认上下文',
+ title: 'Default Context',
version: '1.0.0'
} as const;
@@ -37,7 +37,7 @@ export const CONTEXT_STORE_VERSION = '1.0.0' as const;
// UI文本常量
export const CONTEXT_UI_LABELS = {
/** 默认上下文标题模板 */
- DEFAULT_TITLE_TEMPLATE: '上下文', // 将与日期组合使用
+ DEFAULT_TITLE_TEMPLATE: 'Context', // 将与日期组合使用
/** 副本后缀 */
- DUPLICATE_SUFFIX: '(副本)'
+ DUPLICATE_SUFFIX: ' (Copy)'
} as const;
diff --git a/packages/core/src/services/data/manager.ts b/packages/core/src/services/data/manager.ts
index 9fb96c93..ed418e5c 100644
--- a/packages/core/src/services/data/manager.ts
+++ b/packages/core/src/services/data/manager.ts
@@ -71,7 +71,7 @@ export class DataManager implements IDataManager {
data['userSettings'] = await this.preferenceService.exportData();
data['contexts'] = await this.contextRepo.exportData();
} catch (error) {
- console.error('导出数据失败:', error);
+ console.error('Failed to export data:', error);
if (typeof (error as any)?.code === 'string') {
throw toErrorWithCode(error)
}
diff --git a/packages/core/src/services/favorite/manager.ts b/packages/core/src/services/favorite/manager.ts
index c0f89e39..3755384a 100644
--- a/packages/core/src/services/favorite/manager.ts
+++ b/packages/core/src/services/favorite/manager.ts
@@ -64,7 +64,7 @@ export class FavoriteManager implements IFavoriteManager {
this.initialized = true;
this.initState = 'initialized';
} catch (error) {
- console.error('[FavoriteManager] 初始化失败:', error);
+ console.error('[FavoriteManager] Initialization failed:', error);
// 即使初始化失败,也标记为已初始化,避免阻塞后续操作
this.initialized = true;
this.initState = 'initialized';
@@ -130,7 +130,7 @@ export class FavoriteManager implements IFavoriteManager {
if (migrated) {
// 迁移后更新统计信息
await this.updateStats();
- console.info('[FavoriteManager] 数据迁移完成,已更新收藏项格式');
+ console.info('[FavoriteManager] Legacy data migration completed; favorite entries updated');
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
@@ -184,7 +184,7 @@ export class FavoriteManager implements IFavoriteManager {
await this.storageProvider.setItem('favorite_categories_initialized', 'true');
}
} catch (error) {
- console.warn('[FavoriteManager] 确保默认分类失败:', error);
+ console.warn('[FavoriteManager] Failed to ensure default categories:', error);
}
}
@@ -444,7 +444,7 @@ export class FavoriteManager implements IFavoriteManager {
await this.updateFavorite(id, { useCount: (await this.getFavorite(id)).useCount + 1 });
} catch (error) {
// 静默处理使用次数增加失败,不影响主要功能
- console.warn('增加使用次数失败:', error);
+ console.warn('Failed to increment use count:', error);
}
}
@@ -614,7 +614,7 @@ export class FavoriteManager implements IFavoriteManager {
try {
await this.storageProvider.setItem(this.STORAGE_KEYS.STATS, JSON.stringify(stats));
} catch (error) {
- console.warn('缓存统计数据失败:', error);
+ console.warn('Failed to cache statistics:', error);
}
return stats;
@@ -643,7 +643,7 @@ export class FavoriteManager implements IFavoriteManager {
const independentTags: FavoriteTag[] = storedTags ? JSON.parse(storedTags) : [];
return independentTags.map(t => t.tag);
} catch (error) {
- console.warn('获取独立标签失败:', error);
+ console.warn('Failed to get standalone tags:', error);
return [];
}
}
diff --git a/packages/core/src/services/image-model/manager.ts b/packages/core/src/services/image-model/manager.ts
index 016d6544..6fe38587 100644
--- a/packages/core/src/services/image-model/manager.ts
+++ b/packages/core/src/services/image-model/manager.ts
@@ -443,7 +443,7 @@ export class ImageModelManager implements IImageModelManager {
provider: {
id: config.providerId || 'unknown',
name: `Unknown Provider (${config.providerId || 'unknown'})`,
- description: '此配置损坏,无法修复',
+ description: 'This configuration is corrupted and cannot be repaired.',
requiresApiKey: false,
supportsDynamicModels: false,
defaultBaseURL: '',
@@ -452,7 +452,7 @@ export class ImageModelManager implements IImageModelManager {
model: {
id: config.modelId || 'unknown',
name: `Unknown Model (${config.modelId || 'unknown'})`,
- description: '此配置损坏,请删除后重新创建',
+ description: 'This configuration is corrupted. Please delete it and create a new one.',
providerId: config.providerId || 'unknown',
capabilities: {
text2image: false,
diff --git a/packages/core/src/services/image/adapters/registry.ts b/packages/core/src/services/image/adapters/registry.ts
index 430fd2e1..bae4840a 100644
--- a/packages/core/src/services/image/adapters/registry.ts
+++ b/packages/core/src/services/image/adapters/registry.ts
@@ -95,7 +95,7 @@ export class ImageAdapterRegistry
* 获取错误消息的提供商类型描述
*/
protected getProviderTypeDescription(): string {
- return '图像提供商'
+ return 'image provider'
}
}
diff --git a/packages/core/src/services/llm/adapters/registry.ts b/packages/core/src/services/llm/adapters/registry.ts
index 752a98d8..3db79ca7 100644
--- a/packages/core/src/services/llm/adapters/registry.ts
+++ b/packages/core/src/services/llm/adapters/registry.ts
@@ -114,7 +114,7 @@ export class TextAdapterRegistry
* 获取错误消息的提供商类型描述
*/
protected getProviderTypeDescription(): string {
- return '文本模型提供商';
+ return 'text model provider';
}
}
diff --git a/packages/core/src/services/prompt/service.ts b/packages/core/src/services/prompt/service.ts
index c2b6af82..6eaac785 100644
--- a/packages/core/src/services/prompt/service.ts
+++ b/packages/core/src/services/prompt/service.ts
@@ -845,7 +845,7 @@ export class PromptService implements IPromptService {
return JSON.stringify(
request.inputImages.map((image, index) => ({
index: index + 1,
- label: `图${index + 1}`,
+ label: `Image ${index + 1}`,
mimeType: image.mimeType || "image/png",
})),
);
diff --git a/packages/core/src/services/storage/dexieStorageProvider.ts b/packages/core/src/services/storage/dexieStorageProvider.ts
index 6c0d742d..5d998f6c 100644
--- a/packages/core/src/services/storage/dexieStorageProvider.ts
+++ b/packages/core/src/services/storage/dexieStorageProvider.ts
@@ -97,7 +97,7 @@ export class DexieStorageProvider implements IStorageProvider {
const record = await this.db.storage.get(key);
return record?.value ?? null;
} catch (error) {
- console.error(`获取存储项失败 (${key}):`, error);
+ console.error(`Failed to get storage item (${key}):`, error);
throw new StorageError(`Failed to get item: ${key}`, 'read');
}
}
@@ -115,7 +115,7 @@ export class DexieStorageProvider implements IStorageProvider {
timestamp: Date.now()
});
} catch (error) {
- console.error(`设置存储项失败 (${key}):`, error);
+ console.error(`Failed to set storage item (${key}):`, error);
throw new StorageError(`Failed to set item: ${key}`, 'write');
}
}
@@ -129,7 +129,7 @@ export class DexieStorageProvider implements IStorageProvider {
try {
await this.db.storage.delete(key);
} catch (error) {
- console.error(`删除存储项失败 (${key}):`, error);
+ console.error(`Failed to remove storage item (${key}):`, error);
throw new StorageError(`Failed to remove item: ${key}`, 'delete');
}
}
@@ -143,7 +143,7 @@ export class DexieStorageProvider implements IStorageProvider {
try {
await this.db.storage.clear();
} catch (error) {
- console.error('清空存储失败:', error);
+ console.error('Failed to clear storage:', error);
throw new StorageError('Failed to clear storage', 'clear');
}
}
@@ -209,7 +209,7 @@ export class DexieStorageProvider implements IStorageProvider {
return; // 成功,直接返回
} catch (error) {
lastError = error as Error;
- console.warn(`原子更新尝试 ${attempt}/${maxRetries} 失败 (${key}):`, error);
+ console.warn(`Atomic update attempt ${attempt}/${maxRetries} failed (${key}):`, error);
// 如果是事务错误且还有重试机会,等待一段时间后重试
if (this.isError(error) && error.name === 'PrematureCommitError' && attempt < maxRetries) {
@@ -220,13 +220,13 @@ export class DexieStorageProvider implements IStorageProvider {
// 如果是最后一次尝试或非事务错误,尝试降级到简单更新
if (attempt === maxRetries) {
- console.warn(`所有重试失败,尝试降级到简单更新 (${key})`);
+ console.warn(`All retries failed; falling back to simple update (${key})`);
try {
await this._performSimpleUpdate(key, updateFn);
- console.log(`降级更新成功 (${key})`);
+ console.log(`Fallback update succeeded (${key})`);
return;
} catch (fallbackError) {
- console.error(`降级更新也失败 (${key}):`, fallbackError);
+ console.error(`Fallback update also failed (${key}):`, fallbackError);
throw lastError; // 抛出原始错误
}
}
@@ -263,7 +263,7 @@ export class DexieStorageProvider implements IStorageProvider {
timestamp: Date.now()
});
} catch (error) {
- console.error(`简单更新失败 (${key}):`, error);
+ console.error(`Simple update failed (${key}):`, error);
throw new StorageError(`Failed to perform simple update: ${key}`, 'write');
}
}
@@ -296,12 +296,12 @@ export class DexieStorageProvider implements IStorageProvider {
});
} catch (innerError) {
// 事务内部错误,让事务回滚
- console.error(`事务内部操作失败 (${key}):`, innerError);
+ console.error(`Transaction operation failed (${key}):`, innerError);
throw innerError;
}
});
} catch (error) {
- console.error(`原子更新失败 (${key}):`, error);
+ console.error(`Atomic update failed (${key}):`, error);
// 如果是Dexie事务错误,提供更详细的错误信息
if (this.isError(error) && error.name === 'PrematureCommitError') {
@@ -353,7 +353,7 @@ export class DexieStorageProvider implements IStorageProvider {
}
});
} catch (error) {
- console.error('批量更新失败:', error);
+ console.error('Batch update failed:', error);
throw new StorageError('Failed to perform batch update', 'write');
}
}
@@ -387,7 +387,7 @@ export class DexieStorageProvider implements IStorageProvider {
lastUpdated: lastRecord?.timestamp ?? null
};
} catch (error) {
- console.error('获取存储信息失败:', error);
+ console.error('Failed to get storage information:', error);
return {
itemCount: 0,
estimatedSize: 0,
@@ -412,7 +412,7 @@ export class DexieStorageProvider implements IStorageProvider {
return result;
} catch (error) {
- console.error('导出数据失败:', error);
+ console.error('Failed to export data:', error);
throw new StorageError('Failed to export data', 'read');
}
}
@@ -432,7 +432,7 @@ export class DexieStorageProvider implements IStorageProvider {
await this.db.storage.bulkPut(records);
} catch (error) {
- console.error('导入数据失败:', error);
+ console.error('Failed to import data:', error);
throw new StorageError('Failed to import data', 'write');
}
}
@@ -444,7 +444,7 @@ export class DexieStorageProvider implements IStorageProvider {
try {
await this.db.close();
} catch (error) {
- console.error('关闭数据库失败:', error);
+ console.error('Failed to close database:', error);
}
}
}
diff --git a/packages/core/src/services/storage/fileStorageProvider.ts b/packages/core/src/services/storage/fileStorageProvider.ts
index c850bcbf..bcd27263 100644
--- a/packages/core/src/services/storage/fileStorageProvider.ts
+++ b/packages/core/src/services/storage/fileStorageProvider.ts
@@ -458,8 +458,9 @@ export class FileStorageProvider implements IStorageProvider {
if (error instanceof Error &&
(error.name.includes('Error') ||
error.constructor.name !== 'Error' ||
- error.message.includes('模型') ||
- error.message.includes('不存在'))) {
+ error.message.includes('Model') ||
+ error.message.includes('not found') ||
+ error.message.includes('not exist'))) {
throw error;
}
// 只有真正的存储错误才包装为StorageError
diff --git a/packages/core/src/services/template/manager.ts b/packages/core/src/services/template/manager.ts
index e599da63..00778a63 100644
--- a/packages/core/src/services/template/manager.ts
+++ b/packages/core/src/services/template/manager.ts
@@ -271,8 +271,8 @@ export class TemplateManager implements ITemplateManager {
case 'zh-CN':
return this.staticLoader.getDefaultTemplates();
default:
- console.warn(`Unsupported language: ${language}, falling back to Chinese templates`);
- return this.staticLoader.getDefaultTemplates();
+ console.warn(`Unsupported language: ${language}, falling back to English templates`);
+ return this.staticLoader.getDefaultTemplatesEn();
}
}
@@ -385,7 +385,7 @@ export class TemplateManager implements ITemplateManager {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 6);
finalTemplateId = `user-${template.id}-${timestamp}-${random}`;
- finalTemplateName = `${template.name} (导入副本)`;
+ finalTemplateName = `${template.name} (Imported copy)`;
console.warn(`Detected conflict with built-in template ID: ${template.id}, renamed to: ${finalTemplateId}`);
}
diff --git a/packages/core/src/services/template/processor.ts b/packages/core/src/services/template/processor.ts
index 302e2af8..bd564ef7 100644
--- a/packages/core/src/services/template/processor.ts
+++ b/packages/core/src/services/template/processor.ts
@@ -227,14 +227,14 @@ export class TemplateProcessor {
return tools
.map((tool) => {
const func = tool.function;
- let toolText = `工具名称: ${func.name}`;
+ let toolText = `Tool name: ${func.name}`;
if (func.description) {
- toolText += `\n描述: ${func.description}`;
+ toolText += `\nDescription: ${func.description}`;
}
if (func.parameters) {
- toolText += `\n参数结构: ${JSON.stringify(func.parameters, null, 2)}`;
+ toolText += `\nParameters: ${JSON.stringify(func.parameters, null, 2)}`;
}
return toolText;
diff --git a/packages/core/src/services/template/static-loader.ts b/packages/core/src/services/template/static-loader.ts
index 63c56dcf..2c974454 100644
--- a/packages/core/src/services/template/static-loader.ts
+++ b/packages/core/src/services/template/static-loader.ts
@@ -68,7 +68,7 @@ export class StaticLoader {
}
try {
- console.log(`🔄 静态导入开始加载模板...`);
+ console.log('Starting static template loading...');
const all: Record = {};
const byLanguage: Record> = { zh: {}, en: {} };
@@ -95,7 +95,7 @@ export class StaticLoader {
// 验证内置模板必须包含language字段
if (template.isBuiltin && !language) {
- console.error(`❌ 内置模板缺少language字段: ${id}`);
+ console.error(`Built-in template is missing the language field: ${id}`);
throw new TemplateValidationError(
`Built-in template '${id}' is missing required 'language' field in metadata`,
);
@@ -157,10 +157,10 @@ export class StaticLoader {
const result = { all, byLanguage, byType };
- console.log(`✅ 成功加载 ${Object.keys(all).length} 个模板`, {
- '总数': Object.keys(all).length,
- '中文': Object.keys(byLanguage.zh).length,
- '英文': Object.keys(byLanguage.en).length,
+ console.log(`Loaded ${Object.keys(all).length} templates successfully`, {
+ total: Object.keys(all).length,
+ chinese: Object.keys(byLanguage.zh).length,
+ english: Object.keys(byLanguage.en).length,
optimize: Object.keys(byType.optimize.zh).length + Object.keys(byType.optimize.en).length,
'user-optimize': Object.keys(byType['user-optimize'].zh).length + Object.keys(byType['user-optimize'].en).length,
text2imageOptimize: Object.keys(byType.text2imageOptimize.zh).length + Object.keys(byType.text2imageOptimize.en).length,
@@ -180,7 +180,7 @@ export class StaticLoader {
return result;
} catch (error) {
- console.error('❌ 静态导入加载模板失败:', error);
+ console.error('Failed to load templates via static import:', error);
throw new TemplateLoadError(
'static-loader',
`Failed to load static templates: ${error instanceof Error ? error.message : String(error)}`,
diff --git a/packages/core/src/services/variable-extraction/service.ts b/packages/core/src/services/variable-extraction/service.ts
index dacca927..d2c0807a 100644
--- a/packages/core/src/services/variable-extraction/service.ts
+++ b/packages/core/src/services/variable-extraction/service.ts
@@ -146,7 +146,7 @@ export class VariableExtractionService implements IVariableExtractionService {
private buildTemplateContext(request: VariableExtractionRequest): TemplateContext {
const context: TemplateContext = {
promptContent: request.promptContent,
- existingVariableNames: request.existingVariableNames?.join(', ') || '无',
+ existingVariableNames: request.existingVariableNames?.join(', ') || 'None',
hasExistingVariables: !!request.existingVariableNames?.length,
};
diff --git a/packages/core/src/services/variable-value-generation/service.ts b/packages/core/src/services/variable-value-generation/service.ts
index f0df091c..69ecc757 100644
--- a/packages/core/src/services/variable-value-generation/service.ts
+++ b/packages/core/src/services/variable-value-generation/service.ts
@@ -137,7 +137,7 @@ export class VariableValueGenerationService implements IVariableValueGenerationS
const variablesText = request.variables
.map((v, idx) => {
const parts = [`${idx + 1}. ${v.name}`];
- if (v.currentValue) parts.push(`(当前值: ${v.currentValue})`);
+ if (v.currentValue) parts.push(`(current value: ${v.currentValue})`);
if (v.source) parts.push(`[${v.source}]`);
return parts.join(' ');
})
@@ -240,11 +240,11 @@ export class VariableValueGenerationService implements IVariableValueGenerationS
if (requestedNames.has(val.name)) {
// 🔧 检测LLM返回的同名重复
if (valueMap.has(val.name)) {
- console.warn(`[VariableValueGeneration] LLM返回了重复的变量名: ${val.name},后者将覆盖前者`);
+ console.warn(`[VariableValueGeneration] LLM returned a duplicate variable name: ${val.name}. The later value will overwrite the earlier one.`);
}
valueMap.set(val.name, val);
} else {
- console.warn(`[VariableValueGeneration] LLM返回了未请求的变量: ${val.name}`);
+ console.warn(`[VariableValueGeneration] LLM returned a variable that was not requested: ${val.name}`);
}
}
@@ -253,7 +253,7 @@ export class VariableValueGenerationService implements IVariableValueGenerationS
for (const req of requestedVariables) {
const trimmedName = req.name.trim();
if (seenRequestNames.has(trimmedName)) {
- console.warn(`[VariableValueGeneration] 请求列表中存在重复的变量名: ${trimmedName},将返回相同的生成结果`);
+ console.warn(`[VariableValueGeneration] The request list contains a duplicate variable name: ${trimmedName}. The generated result will be reused.`);
}
seenRequestNames.add(trimmedName);
}
@@ -267,11 +267,11 @@ export class VariableValueGenerationService implements IVariableValueGenerationS
return generated;
}
// 缺失的变量用空值补齐
- console.warn(`[VariableValueGeneration] LLM未返回变量 "${trimmedName}",已补齐空值`);
+ console.warn(`[VariableValueGeneration] LLM did not return variable "${trimmedName}". Filling it with an empty value.`);
return {
name: trimmedName,
value: '',
- reason: '(LLM未生成此变量的值)',
+ reason: 'LLM did not generate a value for this variable.',
confidence: 0,
};
});
diff --git a/packages/core/tests/unit/i18n/runtime-english-guards.test.ts b/packages/core/tests/unit/i18n/runtime-english-guards.test.ts
new file mode 100644
index 00000000..6e270f3b
--- /dev/null
+++ b/packages/core/tests/unit/i18n/runtime-english-guards.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from 'vitest'
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
+
+const readCoreSource = (relativePath: string) =>
+ readFileSync(resolve(process.cwd(), relativePath), 'utf8')
+
+describe('core runtime english guards', () => {
+ it('keeps prompt and template services free of hardcoded chinese runtime defaults', () => {
+ const promptServiceSource = readCoreSource('src/services/prompt/service.ts')
+ const templateLanguageSource = readCoreSource('src/services/template/languageService.ts')
+ const templateLanguageProxySource = readCoreSource('src/services/template/electron-language-proxy.ts')
+ const templateManagerSource = readCoreSource('src/services/template/manager.ts')
+ const templateProcessorSource = readCoreSource('src/services/template/processor.ts')
+ const variableExtractionSource = readCoreSource('src/services/variable-extraction/service.ts')
+
+ expect(promptServiceSource).toMatch(/label:\s*`Image \$\{index \+ 1\}`/)
+ expect(promptServiceSource).not.toMatch(/label:\s*`图\$\{index \+ 1\}`/)
+
+ expect(templateLanguageSource).toMatch(/return '中文'/)
+ expect(templateLanguageProxySource).toMatch(/return '中文'/)
+
+ expect(templateManagerSource).toContain('(Imported copy)')
+ expect(templateManagerSource).not.toMatch(/\(导入副本\)/)
+
+ expect(templateProcessorSource).toMatch(/Tool name:/)
+ expect(templateProcessorSource).toMatch(/Description:/)
+ expect(templateProcessorSource).toMatch(/Parameters:/)
+ expect(templateProcessorSource).not.toMatch(/工具名称:/)
+
+ expect(variableExtractionSource).toMatch(/\|\| 'None'/)
+ expect(variableExtractionSource).not.toMatch(/\|\| '无'/)
+ })
+})
diff --git a/packages/core/tests/unit/template/manager.test.ts b/packages/core/tests/unit/template/manager.test.ts
index 9561a799..2bbfef5f 100644
--- a/packages/core/tests/unit/template/manager.test.ts
+++ b/packages/core/tests/unit/template/manager.test.ts
@@ -62,6 +62,18 @@ describe('TemplateManager with Mocked LanguageService', () => {
expect(zhTemplate?.content).toBe('你好');
});
+ it('should fallback to English templates for unsupported languages', async () => {
+ vi.spyOn(languageService, 'getCurrentLanguage').mockReturnValue('invalid-lang' as any);
+
+ const templates = await templateManager.listTemplates();
+ const enTemplate = templates.find(t => t.id === 'test-en');
+ const zhTemplate = templates.find(t => t.id === 'test-zh');
+
+ expect(enTemplate).toBeDefined();
+ expect(zhTemplate).toBeUndefined();
+ expect(enTemplate?.content).toBe('Hello');
+ });
+
it('should get current builtin template language from the language service', async () => {
vi.spyOn(languageService, 'getCurrentLanguage').mockResolvedValue('en-US');
const lang = await templateManager.getCurrentBuiltinTemplateLanguage();
@@ -135,4 +147,4 @@ describe('TemplateManager with Mocked LanguageService', () => {
await expect(templateManager.deleteTemplate('test-en'))
.rejects.toThrow('Cannot delete built-in template: test-en');
});
-});
\ No newline at end of file
+});
diff --git a/packages/core/tests/unit/variable-value-generation/service-runtime.test.ts b/packages/core/tests/unit/variable-value-generation/service-runtime.test.ts
new file mode 100644
index 00000000..f32f2f20
--- /dev/null
+++ b/packages/core/tests/unit/variable-value-generation/service-runtime.test.ts
@@ -0,0 +1,68 @@
+import { describe, it, expect, vi } from 'vitest'
+
+import { createVariableValueGenerationService } from '../../../src/services/variable-value-generation/service'
+
+describe('VariableValueGenerationService runtime messages', () => {
+ it('uses English warnings and fallback reason for alignment edge cases', async () => {
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+
+ const service = createVariableValueGenerationService(
+ {
+ sendMessage: vi.fn(async () =>
+ JSON.stringify({
+ values: [
+ { name: 'topic', value: 'cats', reason: 'first' },
+ { name: 'topic', value: 'dogs', reason: 'second' },
+ { name: 'extra', value: 'unused', reason: 'extra' },
+ ],
+ summary: 'done',
+ }),
+ ),
+ } as any,
+ {
+ getModel: vi.fn(async () => ({ key: 'model-1' })),
+ } as any,
+ {
+ getTemplate: vi.fn(async () => ({
+ id: 'variable-value-generation',
+ content: 'template',
+ })),
+ } as any,
+ )
+
+ const result = await service.generate({
+ promptContent: 'Write about {{topic}} in a {{tone}} style.',
+ generationModelKey: 'model-1',
+ variables: [
+ { name: 'topic' },
+ { name: 'topic' },
+ { name: 'tone' },
+ ],
+ })
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[VariableValueGeneration] LLM returned a duplicate variable name: topic. The later value will overwrite the earlier one.',
+ )
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[VariableValueGeneration] LLM returned a variable that was not requested: extra',
+ )
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[VariableValueGeneration] The request list contains a duplicate variable name: topic. The generated result will be reused.',
+ )
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[VariableValueGeneration] LLM did not return variable "tone". Filling it with an empty value.',
+ )
+ expect(result.values).toEqual([
+ { name: 'topic', value: 'dogs', reason: 'second', confidence: undefined },
+ { name: 'topic', value: 'dogs', reason: 'second', confidence: undefined },
+ {
+ name: 'tone',
+ value: '',
+ reason: 'LLM did not generate a value for this variable.',
+ confidence: 0,
+ },
+ ])
+
+ consoleWarnSpy.mockRestore()
+ })
+})
diff --git a/packages/mcp-server/src/adapters/error-handler.ts b/packages/mcp-server/src/adapters/error-handler.ts
index 2f5bb684..65965c32 100644
--- a/packages/mcp-server/src/adapters/error-handler.ts
+++ b/packages/mcp-server/src/adapters/error-handler.ts
@@ -16,6 +16,13 @@ export const MCP_ERROR_CODES = {
INVALID_REQUEST: -32600
} as const;
+const INVALID_PARAM_PATTERNS = [
+ 'must be a non-empty string',
+ 'must not exceed',
+ 'is required',
+ 'invalid parameter',
+] as const;
+
export class MCPErrorHandler {
/**
* 转换 Core 模块错误为 MCP 错误
@@ -25,13 +32,13 @@ export class MCPErrorHandler {
if (error.name.includes('OptimizationError') || error.name.includes('IterationError') || error.name.includes('TestError')) {
return new McpError(
MCP_ERROR_CODES.PROMPT_OPTIMIZATION_FAILED,
- `提示词优化失败: ${error.message}`,
+ `Prompt optimization failed: ${error.message}`,
{ originalError: error.name }
);
}
// 参数验证错误
- if (error.message.includes('必须是') || error.message.includes('不能为空') || error.message.includes('过长')) {
+ if (INVALID_PARAM_PATTERNS.some((pattern) => error.message.toLowerCase().includes(pattern))) {
return new McpError(
MCP_ERROR_CODES.INVALID_PARAMS,
error.message,
@@ -43,7 +50,7 @@ export class MCPErrorHandler {
if (error.message.includes('Model') || error.message.includes('API key') || error.message.includes('Template')) {
return new McpError(
MCP_ERROR_CODES.INTERNAL_ERROR,
- `配置错误: ${error.message}`,
+ `Configuration error: ${error.message}`,
{ originalError: error.name }
);
}
@@ -51,7 +58,7 @@ export class MCPErrorHandler {
// 默认内部错误
return new McpError(
MCP_ERROR_CODES.INTERNAL_ERROR,
- `内部错误: ${error.message}`,
+ `Internal error: ${error.message}`,
{ originalError: error.name }
);
}
@@ -60,7 +67,7 @@ export class MCPErrorHandler {
* 创建参数验证错误
*/
static createValidationError(message: string): McpError {
- return new McpError(MCP_ERROR_CODES.INVALID_PARAMS, `参数验证失败: ${message}`);
+ return new McpError(MCP_ERROR_CODES.INVALID_PARAMS, `Parameter validation failed: ${message}`);
}
/**
diff --git a/packages/mcp-server/src/adapters/language-service.ts b/packages/mcp-server/src/adapters/language-service.ts
index d41ef2c4..3112c26c 100644
--- a/packages/mcp-server/src/adapters/language-service.ts
+++ b/packages/mcp-server/src/adapters/language-service.ts
@@ -10,7 +10,7 @@ export class SimpleLanguageService implements ITemplateLanguageService {
private currentLanguage: BuiltinTemplateLanguage;
private initialized = false;
- constructor(defaultLanguage: string = 'zh-CN') {
+ constructor(defaultLanguage: string = 'en-US') {
// 映射语言代码到 Core 模块支持的格式
const languageMap: Record = {
'zh': 'zh-CN',
@@ -21,7 +21,7 @@ export class SimpleLanguageService implements ITemplateLanguageService {
'english': 'en-US'
};
- this.currentLanguage = languageMap[defaultLanguage as keyof typeof languageMap] || 'zh-CN';
+ this.currentLanguage = languageMap[defaultLanguage as keyof typeof languageMap] || 'en-US';
}
async initialize(): Promise {
@@ -51,7 +51,7 @@ export class SimpleLanguageService implements ITemplateLanguageService {
}
async getSupportedLanguages(): Promise {
- return ['zh-CN', 'en-US'];
+ return ['en-US', 'zh-CN'];
}
getLanguageDisplayName(language: BuiltinTemplateLanguage): string {
@@ -71,5 +71,5 @@ export class SimpleLanguageService implements ITemplateLanguageService {
}
export function createSimpleLanguageService(defaultLanguage?: string): SimpleLanguageService {
- return new SimpleLanguageService(defaultLanguage || 'zh-CN');
+ return new SimpleLanguageService(defaultLanguage || 'en-US');
}
diff --git a/packages/mcp-server/src/adapters/parameter-adapter.ts b/packages/mcp-server/src/adapters/parameter-adapter.ts
index 22d7c56f..f510935a 100644
--- a/packages/mcp-server/src/adapters/parameter-adapter.ts
+++ b/packages/mcp-server/src/adapters/parameter-adapter.ts
@@ -10,10 +10,10 @@ export class ParameterValidator {
*/
static validatePrompt(prompt: string): void {
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
- throw new Error('提示词必须是非空字符串');
+ throw new Error('Prompt must be a non-empty string');
}
if (prompt.length > 50000) {
- throw new Error('提示词过长(最大 50,000 字符)');
+ throw new Error('Prompt must not exceed 50,000 characters');
}
}
@@ -22,7 +22,7 @@ export class ParameterValidator {
*/
static validateTemplate(template?: string): void {
if (template !== undefined && (typeof template !== 'string' || template.trim().length === 0)) {
- throw new Error('模板必须是非空字符串');
+ throw new Error('Template must be a non-empty string');
}
}
@@ -31,10 +31,10 @@ export class ParameterValidator {
*/
static validateRequirements(requirements: string): void {
if (!requirements || typeof requirements !== 'string' || requirements.trim().length === 0) {
- throw new Error('需求描述必须是非空字符串');
+ throw new Error('Requirements must be a non-empty string');
}
if (requirements.length > 10000) {
- throw new Error('需求描述过长(最大 10,000 字符)');
+ throw new Error('Requirements must not exceed 10,000 characters');
}
}
}
diff --git a/packages/mcp-server/src/config/templates.ts b/packages/mcp-server/src/config/templates.ts
index 5be63bef..036fbc4a 100644
--- a/packages/mcp-server/src/config/templates.ts
+++ b/packages/mcp-server/src/config/templates.ts
@@ -52,7 +52,7 @@ export async function getTemplateOptions(
const options = templates.map(template => ({
value: template.id,
label: template.name,
- description: template.metadata.description || (template.isBuiltin ? '内置模板' : '用户模板')
+ description: template.metadata.description || (template.isBuiltin ? 'Built-in template' : 'User template')
}));
return options;
diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts
index 854a68af..7f8eb891 100644
--- a/packages/mcp-server/src/index.ts
+++ b/packages/mcp-server/src/index.ts
@@ -1,7 +1,7 @@
#!/usr/bin/env node
/*
- * Prompt Optimizer - AI提示词优化工具
+ * Prompt Optimizer - AI prompt optimization toolkit
* Copyright (C) 2025 linshenkx
*
* This program is free software: you can redistribute it and/or modify
@@ -20,14 +20,14 @@
/**
* MCP Server for Prompt Optimizer
*
- * 提供3个核心工具:
- * - optimize-user-prompt: 优化用户提示词
- * - optimize-system-prompt: 优化系统提示词
- * - iterate-prompt: 迭代优化成熟提示词
+ * Provides 3 core tools:
+ * - optimize-user-prompt: optimize user prompts
+ * - optimize-system-prompt: optimize system prompts
+ * - iterate-prompt: iterate on existing prompts
*
- * 支持 stdio 和 HTTP 两种传输方式
+ * Supports both stdio and HTTP transports
*
- * 注意:环境变量通过 environment.ts 在应用启动时加载
+ * Note: environment variables are loaded by environment.ts during startup
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -65,7 +65,7 @@ async function createServerInstance(config: MCPServerConfig) {
async function setupServerHandlers(server: Server, coreServices: CoreServicesManager) {
// 获取模板选项和默认模板ID用于工具定义
- logger.info('获取模板选项...');
+ logger.info('Loading template options...');
const templateManager = coreServices.getTemplateManager();
const [userOptimizeOptions, systemOptimizeOptions, iterateOptions, userDefaultId, systemDefaultId, iterateDefaultId] = await Promise.all([
getTemplateOptions(templateManager, 'userOptimize'),
@@ -77,23 +77,23 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
]);
// 注册工具列表处理器
- logger.info('注册 MCP 工具...');
+ logger.info('Registering MCP tools...');
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "optimize-user-prompt",
- description: "优化用户提示词,提升与AI对话的效果。适用于日常对话、问答、创作等场景。\n\n主要功能:\n- 增强表达清晰度和具体性\n- 添加必要的上下文信息\n- 优化语言表达和逻辑结构\n- 提高AI理解准确性\n\n使用场景示例:\n- 将模糊问题转化为具体明确的询问\n- 为创作任务添加详细要求和约束\n- 优化技术问题的描述方式",
+ description: "Optimize a user prompt to improve clarity, specificity, and overall response quality. Best for everyday chat, Q&A, writing, and task-oriented requests.\n\nKey capabilities:\n- Make the request clearer and more specific\n- Add missing context when useful\n- Improve wording and logical structure\n- Help the model understand the goal more accurately\n\nTypical use cases:\n- Turn a vague question into a concrete request\n- Add detailed constraints to a creative task\n- Improve the framing of a technical question",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
- description: "要优化的用户提示词。例如:'帮我写个文章' 或 '解释一下机器学习'"
+ description: "The user prompt to optimize. For example: 'Help me write an article' or 'Explain machine learning'."
},
template: {
type: "string",
- description: `选择优化模板,不同模板适用于不同场景:\n${userOptimizeOptions.map(opt => `- ${opt.label}:${opt.description}`).join('\n')}`,
+ description: `Choose an optimization template. Different templates fit different scenarios:\n${userOptimizeOptions.map(opt => `- ${opt.label}: ${opt.description}`).join('\n')}`,
enum: userOptimizeOptions.map(opt => opt.value),
default: userDefaultId
}
@@ -103,17 +103,17 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
},
{
name: "optimize-system-prompt",
- description: "优化系统提示词,提升AI角色扮演和行为控制效果。适用于定制AI助手、创建专业角色、设计对话系统等场景。\n\n主要功能:\n- 增强角色定义和专业性\n- 优化行为指导和约束\n- 改进指令结构和层次\n- 添加必要的专业知识\n\n使用场景示例:\n- 将简单角色描述转化为专业角色定义\n- 为AI助手添加详细的行为规则和限制\n- 优化特定领域专家的知识框架",
+ description: "Optimize a system prompt to improve role definition, instruction quality, and behavior control. Best for custom assistants, expert roles, and structured dialogue systems.\n\nKey capabilities:\n- Strengthen role definition and professionalism\n- Improve behavioral guidance and constraints\n- Refine instruction structure and hierarchy\n- Add missing domain context when needed\n\nTypical use cases:\n- Turn a simple role description into a professional system prompt\n- Add clearer operating rules for an AI assistant\n- Improve a domain-specific expert prompt",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
- description: "要优化的系统提示词。例如:'你是一个助手' 或 '你是一个医疗顾问'"
+ description: "The system prompt to optimize. For example: 'You are a helpful assistant' or 'You are a medical advisor'."
},
template: {
type: "string",
- description: `选择优化模板,不同模板适用于不同场景:\n${systemOptimizeOptions.map(opt => `- ${opt.label}:${opt.description}`).join('\n')}`,
+ description: `Choose an optimization template. Different templates fit different scenarios:\n${systemOptimizeOptions.map(opt => `- ${opt.label}: ${opt.description}`).join('\n')}`,
enum: systemOptimizeOptions.map(opt => opt.value),
default: systemDefaultId
}
@@ -123,21 +123,21 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
},
{
name: "iterate-prompt",
- description: "基于具体需求迭代改进已有的提示词。适用于已经有基础提示词,但需要针对特定需求进行精细调整的场景。\n\n主要功能:\n- 保持原有提示词的核心功能\n- 根据具体需求进行针对性改进\n- 解决现有提示词的特定问题\n- 适应新的使用场景或要求\n\n使用场景示例:\n- 现有提示词效果不够理想,需要改进\n- 需要适应新的业务需求或使用场景\n- 要解决特定的输出格式或内容问题\n- 需要增强某个特定方面的表现",
+ description: "Iteratively improve an existing prompt based on concrete requirements. Best when you already have a usable prompt but need targeted refinements.\n\nKey capabilities:\n- Preserve the prompt's core intent\n- Improve it against specific requirements\n- Address known weaknesses or output issues\n- Adapt it to new scenarios or constraints\n\nTypical use cases:\n- Improve a prompt that is close but not good enough\n- Adapt a prompt to new business or product needs\n- Fix output formatting or content issues\n- Strengthen a specific aspect of performance",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
- description: "要迭代改进的现有提示词。应该是一个已经在使用但需要改进的完整提示词"
+ description: "The existing prompt to refine. This should be a complete prompt that is already in use but needs improvement."
},
requirements: {
type: "string",
- description: "具体的改进需求或问题描述。例如:'输出格式不够规范' 或 '需要更专业的语言风格' 或 '希望增加创意性'"
+ description: "The concrete improvement requirements or problem statement. For example: 'The output format is inconsistent', 'Use a more professional tone', or 'Increase creativity'."
},
template: {
type: "string",
- description: `选择迭代优化模板,不同模板有不同的改进策略:\n${iterateOptions.map(opt => `- ${opt.label}:${opt.description}`).join('\n')}`,
+ description: `Choose an iteration template. Different templates use different refinement strategies:\n${iterateOptions.map(opt => `- ${opt.label}: ${opt.description}`).join('\n')}`,
enum: iterateOptions.map(opt => opt.value),
default: iterateDefaultId
}
@@ -152,7 +152,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
// 注册工具调用处理器
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
- logger.info(`处理工具调用请求: ${name}`);
+ logger.info(`Handling tool call request: ${name}`);
try {
switch (name) {
@@ -164,7 +164,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:缺少必需参数 'prompt'"
+ text: "Error: Missing required parameter 'prompt'"
}]
};
}
@@ -187,7 +187,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:MCP 默认模型未配置或未启用,请检查环境变量配置"
+ text: "Error: The MCP default model is not configured or not enabled. Check the environment configuration."
}]
};
}
@@ -216,7 +216,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:缺少必需参数 'prompt'"
+ text: "Error: Missing required parameter 'prompt'"
}]
};
}
@@ -239,7 +239,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:MCP 默认模型未配置或未启用,请检查环境变量配置"
+ text: "Error: The MCP default model is not configured or not enabled. Check the environment configuration."
}]
};
}
@@ -272,7 +272,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:缺少必需参数 'prompt'"
+ text: "Error: Missing required parameter 'prompt'"
}]
};
}
@@ -282,7 +282,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:缺少必需参数 'requirements'"
+ text: "Error: Missing required parameter 'requirements'"
}]
};
}
@@ -306,7 +306,7 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: "错误:MCP 默认模型未配置或未启用,请检查环境变量配置"
+ text: "Error: The MCP default model is not configured or not enabled. Check the environment configuration."
}]
};
}
@@ -333,23 +333,23 @@ async function setupServerHandlers(server: Server, coreServices: CoreServicesMan
isError: true,
content: [{
type: "text",
- text: `错误:未知工具 '${name}'`
+ text: `Error: Unknown tool '${name}'`
}]
};
}
} catch (error) {
- logger.error(`工具执行错误 ${name}:`, error as Error);
+ logger.error(`Tool execution error ${name}:`, error as Error);
return {
isError: true,
content: [{
type: "text",
- text: `工具执行错误: ${(error as Error).message}`
+ text: `Tool execution error: ${(error as Error).message}`
}]
};
}
});
- logger.info('MCP 工具注册成功');
+ logger.info('MCP tools registered successfully');
}
async function main() {
diff --git a/packages/mcp-server/tests/language-service.test.ts b/packages/mcp-server/tests/language-service.test.ts
new file mode 100644
index 00000000..e89f8464
--- /dev/null
+++ b/packages/mcp-server/tests/language-service.test.ts
@@ -0,0 +1,28 @@
+import { describe, expect, it } from 'vitest';
+import { SimpleLanguageService, createSimpleLanguageService } from '../src/adapters/language-service.js';
+
+describe('SimpleLanguageService', () => {
+ it('defaults to en-US when no explicit language is provided', async () => {
+ const service = createSimpleLanguageService();
+
+ await service.initialize();
+
+ expect(await service.getCurrentLanguage()).toBe('en-US');
+ expect(await service.getSupportedLanguages()).toEqual(['en-US', 'zh-CN']);
+ });
+
+ it('falls back to en-US for unsupported language values', async () => {
+ const service = new SimpleLanguageService('fr-FR');
+
+ await service.initialize();
+
+ expect(await service.getCurrentLanguage()).toBe('en-US');
+ });
+
+ it('keeps localized language display names', () => {
+ const service = new SimpleLanguageService();
+
+ expect(service.getLanguageDisplayName('zh-CN')).toBe('中文');
+ expect(service.getLanguageDisplayName('en-US')).toBe('English');
+ });
+});
diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts
index 2c7cf3ca..05e471d0 100644
--- a/packages/mcp-server/tests/tools.test.ts
+++ b/packages/mcp-server/tests/tools.test.ts
@@ -23,66 +23,66 @@ describe('MCP Server Tools', () => {
});
describe('ParameterValidator', () => {
- it('应该正确验证提示词输入', () => {
- expect(() => ParameterValidator.validatePrompt('有效的提示词')).not.toThrow();
- expect(() => ParameterValidator.validatePrompt('')).toThrow('提示词必须是非空字符串');
- expect(() => ParameterValidator.validatePrompt(' ')).toThrow('提示词必须是非空字符串');
- expect(() => ParameterValidator.validatePrompt('a'.repeat(60000))).toThrow('提示词过长');
+ it('validates prompt input', () => {
+ expect(() => ParameterValidator.validatePrompt('valid prompt')).not.toThrow();
+ expect(() => ParameterValidator.validatePrompt('')).toThrow('Prompt must be a non-empty string');
+ expect(() => ParameterValidator.validatePrompt(' ')).toThrow('Prompt must be a non-empty string');
+ expect(() => ParameterValidator.validatePrompt('a'.repeat(60000))).toThrow('Prompt must not exceed 50,000 characters');
});
- it('应该正确验证需求输入', () => {
- expect(() => ParameterValidator.validateRequirements('有效的需求描述')).not.toThrow();
- expect(() => ParameterValidator.validateRequirements('')).toThrow('需求描述必须是非空字符串');
- expect(() => ParameterValidator.validateRequirements(' ')).toThrow('需求描述必须是非空字符串');
- expect(() => ParameterValidator.validateRequirements('a'.repeat(15000))).toThrow('需求描述过长');
+ it('validates requirements input', () => {
+ expect(() => ParameterValidator.validateRequirements('valid requirements')).not.toThrow();
+ expect(() => ParameterValidator.validateRequirements('')).toThrow('Requirements must be a non-empty string');
+ expect(() => ParameterValidator.validateRequirements(' ')).toThrow('Requirements must be a non-empty string');
+ expect(() => ParameterValidator.validateRequirements('a'.repeat(15000))).toThrow('Requirements must not exceed 10,000 characters');
});
- it('应该正确验证模板输入', () => {
+ it('validates template input', () => {
expect(() => ParameterValidator.validateTemplate('valid-template')).not.toThrow();
expect(() => ParameterValidator.validateTemplate(undefined)).not.toThrow();
- expect(() => ParameterValidator.validateTemplate('')).toThrow('模板必须是非空字符串');
- expect(() => ParameterValidator.validateTemplate(' ')).toThrow('模板必须是非空字符串');
+ expect(() => ParameterValidator.validateTemplate('')).toThrow('Template must be a non-empty string');
+ expect(() => ParameterValidator.validateTemplate(' ')).toThrow('Template must be a non-empty string');
});
});
describe('MCPErrorHandler', () => {
- it('应该正确转换验证错误', () => {
- const error = new Error('提示词必须是非空字符串');
+ it('converts validation errors', () => {
+ const error = new Error('Prompt must be a non-empty string');
const mcpError = MCPErrorHandler.convertCoreError(error);
expect(mcpError.code).toBe(MCP_ERROR_CODES.INVALID_PARAMS); // -32602
- expect(mcpError.message).toContain('提示词必须是非空字符串');
+ expect(mcpError.message).toContain('Prompt must be a non-empty string');
});
- it('应该正确转换优化错误', () => {
- const error = new Error('优化失败');
+ it('converts optimization errors', () => {
+ const error = new Error('Optimization failed');
error.name = 'OptimizationError';
const mcpError = MCPErrorHandler.convertCoreError(error);
expect(mcpError.code).toBe(MCP_ERROR_CODES.PROMPT_OPTIMIZATION_FAILED); // -32001
- expect(mcpError.message).toContain('提示词优化失败');
+ expect(mcpError.message).toContain('Prompt optimization failed');
});
- it('应该将未知错误处理为内部错误', () => {
- const error = new Error('未知错误');
+ it('converts unknown errors to internal errors', () => {
+ const error = new Error('Unknown failure');
const mcpError = MCPErrorHandler.convertCoreError(error);
expect(mcpError.code).toBe(MCP_ERROR_CODES.INTERNAL_ERROR); // -32000
- expect(mcpError.message).toContain('内部错误');
+ expect(mcpError.message).toContain('Internal error');
});
- it('应该正确创建验证错误', () => {
- const mcpError = MCPErrorHandler.createValidationError('测试验证错误');
+ it('creates validation errors', () => {
+ const mcpError = MCPErrorHandler.createValidationError('Validation issue');
expect(mcpError.code).toBe(MCP_ERROR_CODES.INVALID_PARAMS);
- expect(mcpError.message).toContain('参数验证失败: 测试验证错误');
+ expect(mcpError.message).toContain('Parameter validation failed: Validation issue');
});
- it('应该正确创建内部错误', () => {
- const mcpError = MCPErrorHandler.createInternalError('测试内部错误');
+ it('creates internal errors', () => {
+ const mcpError = MCPErrorHandler.createInternalError('Internal failure');
expect(mcpError.code).toBe(MCP_ERROR_CODES.INTERNAL_ERROR);
- expect(mcpError.message).toContain('测试内部错误');
+ expect(mcpError.message).toContain('Internal failure');
});
});
});
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 40b92f93..6a2c43fc 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -20,7 +20,7 @@
"files": [
"dist"
],
- "readme": "在Windows环境下,请使用分号(;)而不是&&作为命令分隔符,或使用build:win脚本",
+ "readme": "On Windows, use semicolons (;) instead of && as command separators, or run the build:win script.",
"scripts": {
"dev": "vite",
"build:bundle": "vite build",
diff --git a/packages/ui/src/components/BuiltinTemplateLanguageSwitch.vue b/packages/ui/src/components/BuiltinTemplateLanguageSwitch.vue
index 9dcfae13..fcd9180c 100644
--- a/packages/ui/src/components/BuiltinTemplateLanguageSwitch.vue
+++ b/packages/ui/src/components/BuiltinTemplateLanguageSwitch.vue
@@ -46,18 +46,18 @@ const toast = useToast()
// 统一使用inject获取services
const services = inject>('services')
if (!services) {
- throw new Error('[BuiltinTemplateLanguageSwitch] services未正确注入,请确保在App组件中正确provide了services')
+ throw new Error('[BuiltinTemplateLanguageSwitch] Services were not injected correctly. Make sure App provides the services dependency.')
}
const getTemplateManager = computed(() => {
const servicesValue = services.value
if (!servicesValue) {
- throw new Error('[BuiltinTemplateLanguageSwitch] services未初始化,请确保应用已正确启动')
+ throw new Error('[BuiltinTemplateLanguageSwitch] Services are not initialized. Make sure the app has finished bootstrapping.')
}
const manager = servicesValue.templateManager
if (!manager) {
- throw new Error('[BuiltinTemplateLanguageSwitch] templateManager未初始化,请确保服务已正确配置')
+ throw new Error('[BuiltinTemplateLanguageSwitch] Template manager is not initialized. Check the service configuration.')
}
return manager
@@ -66,22 +66,33 @@ const getTemplateManager = computed(() => {
const getTemplateLanguageService = computed(() => {
const servicesValue = services.value
if (!servicesValue) {
- throw new Error('[BuiltinTemplateLanguageSwitch] services未初始化,请确保应用已正确启动')
+ throw new Error('[BuiltinTemplateLanguageSwitch] Services are not initialized. Make sure the app has finished bootstrapping.')
}
const service = servicesValue.templateLanguageService
if (!service) {
- throw new Error('[BuiltinTemplateLanguageSwitch] templateLanguageService未初始化,请确保服务已正确配置')
+ throw new Error('[BuiltinTemplateLanguageSwitch] Template language service is not initialized. Check the service configuration.')
}
return service
})
// Reactive state
-const currentLanguage = ref('zh-CN')
+const currentLanguage = ref('en-US')
const supportedLanguages = ref([])
const isChanging = ref(false)
+const getLanguageDisplayNameFallback = (language: BuiltinTemplateLanguage): string => {
+ switch (language) {
+ case 'zh-CN':
+ return '中文'
+ case 'en-US':
+ return 'English'
+ default:
+ return language
+ }
+}
+
// Computed properties
const getCurrentLanguageShort = computed(() => {
try {
@@ -92,7 +103,7 @@ const getCurrentLanguageShort = computed(() => {
return service.getLanguageDisplayName(currentLanguage.value)
} catch (error) {
console.error('Error getting current language short:', error)
- return '中文' // fallback to Chinese
+ return getLanguageDisplayNameFallback(currentLanguage.value)
}
})
@@ -122,8 +133,8 @@ onMounted(async () => {
} catch (error) {
console.error('Failed to initialize builtin template language switch:', error)
// Set fallback values
- currentLanguage.value = 'zh-CN'
- supportedLanguages.value = ['zh-CN', 'en-US']
+ currentLanguage.value = 'en-US'
+ supportedLanguages.value = ['en-US', 'zh-CN']
// Only show toast error if toast is available
try {
diff --git a/packages/ui/src/components/CategoryManager.vue b/packages/ui/src/components/CategoryManager.vue
index 07fdc9ab..514ea651 100644
--- a/packages/ui/src/components/CategoryManager.vue
+++ b/packages/ui/src/components/CategoryManager.vue
@@ -154,6 +154,7 @@ import {
} from '@vicons/tabler';
import { useToast } from '../composables/ui/useToast';
import { useI18n } from 'vue-i18n';
+import { getI18nErrorMessage } from '../utils/error';
import type { FavoriteCategory } from '@prompt-optimizer/core';
import type { AppServices } from '../types/services';
@@ -374,7 +375,7 @@ const handleDeleteCategory = async (category: FavoriteCategory) => {
try {
deletingCategoryUsageCount.value = await servicesValue.favoriteManager.getCategoryUsage(category.id);
} catch (error) {
- console.error('获取分类使用统计失败:', error);
+ console.error('Failed to fetch category usage count:', error);
deletingCategoryUsageCount.value = 0;
}
}
@@ -388,7 +389,7 @@ const handleConfirmDelete = async () => {
const servicesValue = services?.value;
if (!servicesValue?.favoriteManager) {
- message.warning('收藏功能暂不可用');
+ message.warning('Favorites are temporarily unavailable.');
return;
}
@@ -407,9 +408,8 @@ const handleConfirmDelete = async () => {
await loadCategories();
emit('category-updated');
} catch (error) {
- console.error('删除分类失败:', error);
- const errorMessage = error instanceof Error ? error.message : '未知错误';
- message.error(`${t('favorites.categoryManager.deleteFailed')}: ${errorMessage}`);
+ console.error('Failed to delete category:', error);
+ message.error(getI18nErrorMessage(error, t('favorites.categoryManager.deleteFailed')));
} finally {
deleteDialogVisible.value = false;
deletingCategory.value = null;
@@ -426,7 +426,7 @@ const handleSaveCategory = async () => {
const servicesValue = services?.value;
if (!servicesValue?.favoriteManager) {
- message.warning('收藏功能暂不可用');
+ message.warning(t('favorites.manager.messages.unavailable'));
return;
}
@@ -457,9 +457,8 @@ const handleSaveCategory = async () => {
await loadCategories();
emit('category-updated');
} catch (error) {
- console.error('保存分类失败:', error);
- const errorMessage = error instanceof Error ? error.message : '未知错误';
- message.error(`${t('favorites.categoryManager.saveFailed')}: ${errorMessage}`);
+ console.error('Failed to save category:', error);
+ message.error(getI18nErrorMessage(error, t('favorites.categoryManager.saveFailed')));
} finally {
saving.value = false;
}
@@ -494,16 +493,15 @@ const handleUpdateExpandedKeys = (keys: string[]) => {
const loadCategories = async () => {
const servicesValue = services?.value;
if (!servicesValue?.favoriteManager) {
- console.warn('收藏管理器未初始化');
+ console.warn('Favorite manager is not initialized.');
return;
}
try {
categories.value = await servicesValue.favoriteManager.getCategories();
} catch (error) {
- console.error('加载分类失败:', error);
- const errorMessage = error instanceof Error ? error.message : '未知错误';
- message.error(`${t('favorites.categoryManager.loadFailed')}: ${errorMessage}`);
+ console.error('Failed to load categories:', error);
+ message.error(getI18nErrorMessage(error, t('favorites.categoryManager.loadFailed')));
}
};
diff --git a/packages/ui/src/components/CategoryTreeSelect.vue b/packages/ui/src/components/CategoryTreeSelect.vue
index 7c041aa1..fc282759 100644
--- a/packages/ui/src/components/CategoryTreeSelect.vue
+++ b/packages/ui/src/components/CategoryTreeSelect.vue
@@ -120,14 +120,14 @@ const computedStyle = computed(() => props.style);
const loadCategories = async () => {
const servicesValue = services?.value;
if (!servicesValue?.favoriteManager) {
- console.warn('收藏管理器未初始化,跳过分类加载');
+ console.warn('Favorite manager is not initialized; skipping category loading.');
return;
}
try {
categories.value = await servicesValue.favoriteManager.getCategories();
} catch (error) {
- console.error('加载分类失败:', error);
+ console.error('Failed to load categories:', error);
}
};
diff --git a/packages/ui/src/components/DataManager.vue b/packages/ui/src/components/DataManager.vue
index 2abadd92..8840e45d 100644
--- a/packages/ui/src/components/DataManager.vue
+++ b/packages/ui/src/components/DataManager.vue
@@ -227,18 +227,18 @@ const dataManagerModalStyle = {
const services = inject>('services')
if (!services) {
- throw new Error('[DataManager] services未正确注入,请确保在App组件中正确provide了services')
+ throw new Error('[DataManager] Services were not injected correctly. Make sure App provides the services instance.')
}
const getDataManager = computed(() => {
const servicesValue = services.value
if (!servicesValue) {
- throw new Error('[DataManager] services未初始化,请确保应用已正确启动')
+ throw new Error('[DataManager] Services are not initialized. Make sure the application has started correctly.')
}
const manager = servicesValue.dataManager
if (!manager) {
- throw new Error('[DataManager] dataManager未初始化,请确保服务已正确配置')
+ throw new Error('[DataManager] DataManager is not initialized. Make sure the service is configured correctly.')
}
return manager
@@ -285,7 +285,7 @@ const refreshStorageSummary = async () => {
isRefreshingStorage.value = true
const servicesValue = services.value
if (!servicesValue) {
- throw new Error('[DataManager] services未初始化,请确保应用已正确启动')
+ throw new Error('[DataManager] Services are not initialized. Make sure the application has started correctly.')
}
storageSummary.value = await resolveDataManagerStorageBreakdown({
@@ -357,7 +357,7 @@ const handleExport = async () => {
toast.success(t('dataManager.export.success'))
} catch (error) {
- console.error('导出失败:', error)
+ console.error('Export failed:', error)
toast.error(t('dataManager.export.failed'))
} finally {
isExporting.value = false
@@ -389,7 +389,7 @@ const handleImport = async () => {
emit('close')
clearSelectedFile()
} catch (error) {
- console.error('导入失败:', error)
+ console.error('Import failed:', error)
toast.error(`${t('dataManager.import.failed')}: ${(error as Error).message}`)
} finally {
isImporting.value = false
diff --git a/packages/ui/src/components/FavoriteButton.vue b/packages/ui/src/components/FavoriteButton.vue
index db789335..71c3acab 100644
--- a/packages/ui/src/components/FavoriteButton.vue
+++ b/packages/ui/src/components/FavoriteButton.vue
@@ -4,7 +4,7 @@
:size="size"
:disabled="loading"
@click="handleToggleFavorite"
- :title="isFavorited ? '取消收藏' : '添加到收藏'"
+ :title="isFavorited ? t('favorites.button.removeTitle') : t('favorites.button.addTitle')"
class="favorite-button"
>
@@ -13,14 +13,14 @@
- {{ isFavorited ? '已收藏' : '收藏' }}
+ {{ isFavorited ? t('favorites.button.favorited') : t('favorites.button.favorite') }}
-
+
-
+
-
+
-
+
@@ -74,14 +74,14 @@