├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── chatmcp.yaml ├── docker-compose.yml ├── package-lock.json ├── package.json ├── src ├── index.ts ├── text_eval.ts └── utils.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | MCP_PORT=3031 3 | 4 | # 翻译 API 密钥 (服务器端环境变量) 5 | TRANSLATION_API_KEY= 6 | TRANSLATION_MODEL= 7 | TRANSLATION_BASE_URL=https://.../v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 依赖目录 2 | node_modules/ 3 | 4 | # 构建输出 5 | dist/ 6 | 7 | # 环境变量 8 | .env 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | # 日志 15 | logs 16 | *.log 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # 编辑器配置 22 | .vscode/* 23 | !.vscode/extensions.json 24 | .idea/ 25 | *.iml 26 | .DS_Store 27 | 28 | # 测试覆盖率报告 29 | coverage/ 30 | 31 | # 临时文件 32 | tmp/ 33 | temp/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | # 复制package.json和package-lock.json 6 | COPY package*.json ./ 7 | 8 | # 安装依赖 9 | RUN npm ci 10 | 11 | # 复制源代码 12 | COPY . . 13 | 14 | # 构建应用 15 | RUN npm run build 16 | 17 | # 生产阶段 18 | FROM node:18-alpine AS production 19 | 20 | WORKDIR /app 21 | 22 | # 设置环境变量 23 | ENV NODE_ENV=production 24 | ENV PORT=3031 25 | 26 | # 复制构建产物和依赖 27 | COPY --from=builder /app/package*.json ./ 28 | COPY --from=builder /app/dist ./dist 29 | COPY --from=builder /app/node_modules ./node_modules 30 | 31 | # 暴露默认端口 32 | EXPOSE ${PORT} 33 | 34 | # 设置健康检查 35 | HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ 36 | CMD node -e "process.exit(require('net').createConnection(process.env.PORT || 3031).on('error', () => 1).on('connect', () => 0))" 37 | 38 | # 运行应用 39 | CMD ["node", "dist/index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # AiryLark License 2 | # AiryLark许可证 3 | 4 | ## Core License (Apache License 2.0) 5 | ## 核心许可证 (Apache License 2.0) 6 | 7 | Copyright 2025 VCorp AI, Inc. 8 | 9 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and limitations under the License. 15 | 16 | 依照Apache许可证2.0版本("许可证")授权;除非遵守许可证,否则不得使用此文件。 17 | 您可以在以下网址获取许可证副本:http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | 根据适用法律或书面协议,本软件按"原样"分发,不附带任何明示或暗示的担保或条件。 20 | 有关许可证下特定语言的权限和限制,请参阅许可证。 21 | 22 | ## Additional Terms for Specific Features 23 | ## 特定功能的附加条款 24 | 25 | The following additional terms apply to AiryLark's advanced translation features, batch processing, API integration, and enterprise-level functionalities. In the event of a conflict, these terms shall take precedence over the Apache License: 26 | 27 | 1. **Free for Self-Hosted Versions**: All features of AiryLark, including advanced translation features, batch processing, API integration, and enterprise-level functionalities, are always free to use in self-hosted versions. 28 | 29 | 2. **Resale Restrictions**: No third party may sell or offer as a service the advanced translation features, batch processing, API integration, and enterprise-level functionalities without prior written consent from the copyright holder. 30 | 31 | 3. **Distribution of Modifications**: Any modifications to advanced translation features, batch processing, API integration, and enterprise-level functionalities must be freely distributed and may not be sold or offered as a service. 32 | 33 | 以下附加条款适用于AiryLark的高级翻译功能、批量处理、API集成和企业级功能。如有冲突,这些条款优先于Apache许可证: 34 | 35 | 1. **自托管版本免费**:AiryLark的所有功能,包括高级翻译功能、批量处理、API集成和企业级功能,在自托管版本中始终免费使用。 36 | 37 | 2. **转售限制**:未经版权持有者事先书面同意,任何第三方不得销售或作为服务提供高级翻译功能、批量处理、API集成和企业级功能。 38 | 39 | 3. **修改分发**:对高级翻译功能、批量处理、API集成和企业级功能的任何修改必须自由分发,不得销售或作为服务提供。 40 | 41 | For further inquiries or commercial licensing, please contact: [info@vcorp.ai] 42 | 如需进一步咨询或获取商业许可,请联系:[info@vcorp.ai] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AiryLark MCP 专业翻译服务器 2 | 3 | [![License: Custom](https://img.shields.io/badge/License-Custom%20(Apache%202.0%20with%20restrictions)-blue.svg)](../LICENSE) 4 | 5 | 这是AiryLark项目的ModelContextProtocol(MCP)服务器模块,提供专业级高精度翻译服务接口。MCP是一种标准协议,允许智能助手与外部服务进行结构化交互,使复杂翻译能力可直接被Claude等大型AI模型调用。 6 | 7 | ## 专业翻译优势 8 | 9 | - **三阶段翻译流程**:分析规划、分段翻译、全文审校,确保专业领域文档的翻译质量 10 | - **领域术语识别**:自动识别专业文本领域,提取关键术语并确保术语一致性 11 | - **质量评估系统**:提供全面翻译质量评估,包括准确性、流畅性、术语使用和风格一致性 12 | - **多语言支持**:支持中文、英文、日语、韩语、法语、德语等多种语言互译 13 | - **风格与格式保持**:根据文本类型自动调整翻译风格,保持原文的专业性和表达方式 14 | 15 | ## 适用场景 16 | 17 | - **技术文档翻译**:软件文档、API文档、技术规范等专业内容翻译 18 | - **学术论文翻译**:确保学术术语准确,保持学术文体风格 19 | - **法律文件翻译**:保证法律术语准确性和表述精确性 20 | - **医疗资料翻译**:专业医学术语翻译和医疗文献本地化 21 | - **金融报告翻译**:准确翻译金融术语和复杂财务概念 22 | 23 | ## 安装 24 | 25 | 1. 确保已安装Node.js (v18+)和npm 26 | 27 | 2. 安装依赖: 28 | 29 | ```bash 30 | cd mcp-server 31 | npm install 32 | ``` 33 | 34 | 3. 配置环境变量: 35 | 36 | 创建`.env`文件或设置以下环境变量: 37 | 38 | ``` 39 | # 翻译API配置 40 | TRANSLATION_API_KEY=your_api_key 41 | TRANSLATION_MODEL=your_model_name 42 | TRANSLATION_BASE_URL=your_api_base_url 43 | 44 | # 服务器配置 45 | PORT=3031 # MCP服务器端口,可选,默认3031 46 | ``` 47 | 48 | ## 使用方法 49 | 50 | ### 开发环境 51 | 52 | 启动开发服务器: 53 | 54 | ```bash 55 | npm run dev 56 | ``` 57 | 58 | ### 生产环境 59 | 60 | 构建并启动服务器: 61 | 62 | ```bash 63 | npm run build 64 | npm start 65 | ``` 66 | 67 | ## MCP工具接口 68 | 69 | 服务器提供以下MCP标准工具: 70 | 71 | ### 1. 翻译工具 (translate_text) 72 | 73 | 专业级文本翻译,自动适应不同领域和文体风格。 74 | 75 | **参数:** 76 | - `text`: 需要翻译的源文本 77 | - `target_language`: 目标语言代码 (如'zh'、'en'、'ja'等) 78 | - `source_language`: (可选)源语言代码 79 | - `high_quality`: (可选)是否启用高精度翻译流程,默认为true 80 | 81 | **使用场景:** 82 | - 设置`high_quality=true`用于专业文档、学术论文等对精度要求高的场景 83 | - 设置`high_quality=false`用于非正式内容或需要快速翻译的场景 84 | 85 | ### 2. 翻译质量评估工具 (evaluate_translation) 86 | 87 | 对翻译结果进行全面质量评估,提供详细反馈。 88 | 89 | **参数:** 90 | - `original_text`: 原始文本 91 | - `translated_text`: 翻译后的文本 92 | - `detailed_feedback`: (可选)是否提供详细反馈,默认为false 93 | 94 | **评估指标:** 95 | - 准确性:译文是否准确传达原文意思 96 | - 流畅性:译文是否符合目标语言表达习惯 97 | - 术语使用:专业术语翻译的准确性和一致性 98 | - 风格一致性:译文是否保持原文风格 99 | 100 | ### 资源接口 101 | 102 | - **supported_languages**: 支持的语言列表 103 | - URI: `languages://list` 104 | 105 | ## 与AI助手集成 106 | 107 | 本服务器设计为与支持MCP协议的AI助手无缝集成,使AI能够提供专业级翻译服务: 108 | 109 | ```typescript 110 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 111 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; 112 | 113 | // 连接到MCP服务器 114 | const transport = new SSEClientTransport("http://localhost:3031"); 115 | const client = new Client( 116 | { name: "assistant-client", version: "1.0.0" }, 117 | { capabilities: { tools: {} } } 118 | ); 119 | await client.connect(transport); 120 | 121 | // 调用专业翻译工具 122 | const result = await client.callTool({ 123 | name: "translate_text", 124 | arguments: { 125 | text: "The mitochondrion is the powerhouse of the cell.", 126 | target_language: "zh", 127 | high_quality: true 128 | } 129 | }); 130 | 131 | console.log(result.content[0].text); 132 | ``` 133 | 134 | ## Claude Chat与Cursor等MCP客户端配置 135 | 136 | 在支持MCP协议的AI助手应用中,可通过以下方式配置与AiryLark翻译服务器的连接: 137 | 138 | ### Cursor配置 139 | 140 | 在Cursor设置或配置文件中添加以下MCP服务器配置: 141 | 142 | ```json 143 | { 144 | "mcpServers": { 145 | "airylark-translation": { 146 | "url": "https://airylark-mcp.vcorp.ai/sse" 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | ### Claude Chat配置 153 | 154 | 在Claude Chat中,可以通过以下步骤开启MCP服务器连接: 155 | 156 | 1. 进入设置页面 157 | 2. 找到"开发者设置"或"外部工具"选项 158 | 3. 添加新的MCP服务器,填写名称与URL 159 | 4. 服务器URL填写 `https://airylark-mcp.vcorp.ai/sse` 160 | 161 | 配置完成后,AI助手便可以使用"translate_text"和"evaluate_translation"工具,轻松处理各类专业文档翻译需求。 162 | 163 | ## 服务器配置与运行 164 | 165 | AiryLark MCP服务器支持多种部署和运行方式,以下是常用配置方法: 166 | 167 | ### Docker部署 168 | 169 | 使用官方发布的Docker镜像是最简单的部署方式: 170 | 171 | ```bash 172 | # 拉取官方镜像 173 | docker pull wizdy/airylark-mcp-server 174 | 175 | # 运行容器 176 | docker run -p 3031:3031 --env-file .env -d wizdy/airylark-mcp-server 177 | ``` 178 | 179 | ### Docker Compose部署 180 | 181 | 使用项目提供的docker-compose.yml文件,配合官方镜像可以更方便地管理服务: 182 | 183 | ```yaml 184 | # docker-compose.yml 示例 185 | services: 186 | mcp-server: 187 | image: wizdy/airylark-mcp-server 188 | ports: 189 | - "${MCP_PORT}:${MCP_PORT}" 190 | environment: 191 | - NODE_ENV=production 192 | - PORT=${MCP_PORT} 193 | - TRANSLATION_API_KEY=${TRANSLATION_API_KEY} 194 | - TRANSLATION_MODEL=${TRANSLATION_MODEL} 195 | - TRANSLATION_BASE_URL=${TRANSLATION_BASE_URL} 196 | restart: always 197 | ``` 198 | 199 | 运行服务: 200 | 201 | ```bash 202 | # 设置环境变量或创建.env文件 203 | export MCP_PORT=3031 204 | export TRANSLATION_API_KEY=your_api_key 205 | export TRANSLATION_MODEL=your_model_name 206 | export TRANSLATION_BASE_URL=your_api_base_url 207 | 208 | # 启动服务 209 | docker-compose up -d 210 | ``` 211 | 212 | ### 服务器配置示例 213 | 214 | 您也可以使用类似以下的配置方式来定义和启动MCP服务器: 215 | 216 | ```json 217 | { 218 | "mcpServers": { 219 | "airylark-translation": { 220 | "command": "docker", 221 | "args": [ 222 | "run", 223 | "-i", 224 | "--rm", 225 | "-e", 226 | "TRANSLATION_API_KEY", 227 | "-e", 228 | "TRANSLATION_MODEL", 229 | "-e", 230 | "TRANSLATION_BASE_URL", 231 | "wizdy/airylark-mcp-server" 232 | ], 233 | "env": { 234 | "TRANSLATION_API_KEY": "", 235 | "TRANSLATION_MODEL": "", 236 | "TRANSLATION_BASE_URL": "" 237 | } 238 | } 239 | } 240 | } 241 | ``` 242 | 243 | 这种配置方式适用于需要在应用内直接管理MCP服务器生命周期的场景。 244 | 245 | ## 许可证 246 | 247 | 本项目使用与AiryLark主项目相同的定制许可证,详见[LICENSE](LICENSE)文件。 -------------------------------------------------------------------------------- /chatmcp.yaml: -------------------------------------------------------------------------------- 1 | params: 2 | type: object 3 | properties: 4 | TRANSLATION_API_KEY: 5 | type: string 6 | description: API密钥,用于翻译服务 7 | TRANSLATION_BASE_URL: 8 | type: string 9 | description: 翻译API基础URL 10 | default: "https://api.openai.com/v1" 11 | TRANSLATION_MODEL: 12 | type: string 13 | description: 翻译使用的模型名称 14 | default: "gpt-4o" 15 | required: 16 | - TRANSLATION_API_KEY 17 | 18 | rest: 19 | name: translation-server 20 | port: 3031 21 | endpoint: /api 22 | 23 | npx: 24 | command: 25 | | TRANSLATION_API_KEY={TRANSLATION_API_KEY} TRANSLATION_BASE_URL={TRANSLATION_BASE_URL} TRANSLATION_MODEL={TRANSLATION_MODEL} npx -y server-translation-mcp --mode=rest 26 | config: 27 | | { 28 | "mcpServers": { 29 | "translation-server": { 30 | "command": "npx", 31 | "args": [ 32 | "-y", 33 | "server-translation-mcp", 34 | "--mode=rest" 35 | ], 36 | "env": { 37 | "TRANSLATION_API_KEY": "YOUR_API_KEY_HERE", 38 | "TRANSLATION_BASE_URL": "https://api.openai.com/v1", 39 | "TRANSLATION_MODEL": "gpt-4o" 40 | } 41 | } 42 | } 43 | } 44 | 45 | docker: 46 | command: 47 | | docker run -i --rm -e TRANSLATION_API_KEY={TRANSLATION_API_KEY} -e TRANSLATION_BASE_URL={TRANSLATION_BASE_URL} -e TRANSLATION_MODEL={TRANSLATION_MODEL} -p 3031:3031 mcp/translation-server 48 | config: 49 | | { 50 | "mcpServers": { 51 | "translation-server": { 52 | "command": "docker", 53 | "args": [ 54 | "run", 55 | "-i", 56 | "--rm", 57 | "-e", 58 | "TRANSLATION_API_KEY", 59 | "-e", 60 | "TRANSLATION_BASE_URL", 61 | "-e", 62 | "TRANSLATION_MODEL", 63 | "-p", 64 | "3031:3031", 65 | "mcp/translation-server" 66 | ], 67 | "env": { 68 | "TRANSLATION_API_KEY": "YOUR_API_KEY_HERE", 69 | "TRANSLATION_BASE_URL": "https://api.openai.com/v1", 70 | "TRANSLATION_MODEL": "gpt-4o" 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mcp-server: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | ports: 7 | - "${MCP_PORT}:${MCP_PORT}" 8 | environment: 9 | - NODE_ENV=production 10 | - PORT=${MCP_PORT} 11 | - TRANSLATION_API_KEY=${TRANSLATION_API_KEY} 12 | - TRANSLATION_MODEL=${TRANSLATION_MODEL} 13 | - TRANSLATION_BASE_URL=${TRANSLATION_BASE_URL} 14 | restart: always 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "airylark-mcp-server", 3 | "version": "0.1.0", 4 | "main": "dist/index.js", 5 | "description": "AiryLark的ModelContextProtocol(MCP)服务器,提供高精度翻译API", 6 | "license": "SEE LICENSE IN ../LICENSE", 7 | "author": "AiryLark Team", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/wizd/airylark.git", 11 | "directory": "mcp-server" 12 | }, 13 | "keywords": [ 14 | "mcp", 15 | "translation", 16 | "openai", 17 | "api", 18 | "airylark" 19 | ], 20 | "scripts": { 21 | "build": "tsc", 22 | "start": "node dist/index.js", 23 | "dev": "node --loader ts-node/esm src/index.ts", 24 | "lint": "eslint src/**/*.ts", 25 | "test": "jest", 26 | "clean": "rimraf dist", 27 | "prebuild": "npm run clean" 28 | }, 29 | "dependencies": { 30 | "@chatmcp/sdk": "^1.0.5", 31 | "@modelcontextprotocol/sdk": "^1.8.0", 32 | "dotenv": "^16.4.7", 33 | "express": "^5.1.0", 34 | "node-fetch": "^3.3.2", 35 | "zod": "^3.24.2" 36 | }, 37 | "devDependencies": { 38 | "@types/express": "^5.0.1", 39 | "@types/node": "^20", 40 | "eslint": "^9", 41 | "jest": "^29.7.0", 42 | "rimraf": "^5.0.5", 43 | "ts-jest": "^29.1.2", 44 | "ts-node": "^10.9.2", 45 | "typescript": "^5" 46 | }, 47 | "engines": { 48 | "node": ">=18.0.0" 49 | }, 50 | "type": "module" 51 | } 52 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { z } from "zod"; 4 | import fetch from 'node-fetch'; 5 | import dotenv from 'dotenv'; 6 | import { fileURLToPath } from 'url'; 7 | import { dirname, resolve } from 'path'; 8 | import express, { Request, Response, NextFunction } from 'express'; 9 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 10 | import { evaluateTranslationQuality } from "./text_eval.js"; 11 | import { cleanJsonString, segmentText } from "./utils.js"; 12 | import { getParamValue } from "@chatmcp/sdk/utils/index.js"; 13 | import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; 14 | 15 | // 获取当前文件的目录路径 16 | const __filename = fileURLToPath(import.meta.url); 17 | const __dirname = dirname(__filename); 18 | 19 | // 相对于当前文件找到.env文件 20 | dotenv.config({ path: resolve(__dirname, '../../.env') }); 21 | 22 | // 获取配置参数,优先使用命令行参数,其次使用环境变量 23 | const TRANSLATION_BASE_URL = getParamValue("translation_base_url") || process.env.TRANSLATION_BASE_URL; 24 | const TRANSLATION_API_KEY = getParamValue("translation_api_key") || process.env.TRANSLATION_API_KEY; 25 | const TRANSLATION_MODEL = getParamValue("translation_model") || process.env.TRANSLATION_MODEL; 26 | 27 | // 获取服务器模式和端口 28 | const MODE = getParamValue("mode") || process.env.MODE || "sse"; 29 | const PORT = getParamValue("port") || process.env.PORT || 3031; 30 | const ENDPOINT = getParamValue("endpoint") || process.env.ENDPOINT || "/rest"; 31 | 32 | // 定义翻译API响应类型 33 | interface TranslationResponse { 34 | translated_text: string; 35 | source_language?: string; 36 | confidence?: number; 37 | } 38 | 39 | // 翻译计划接口 40 | interface TranslationPlan { 41 | contentType: string; 42 | style: string; 43 | specializedKnowledge: string[]; 44 | keyTerms: Record; 45 | } 46 | 47 | // 创建翻译规划API的响应类型 48 | interface TranslationPlanResponse { 49 | contentType: string; 50 | style: string; 51 | specializedKnowledge: string[]; 52 | keyTerms: Record; 53 | } 54 | 55 | // 翻译段落API的响应类型 56 | interface TranslateSegmentResponse { 57 | translated_text: string; 58 | } 59 | 60 | // 审校译文API的响应类型 61 | interface ReviewTranslationResponse { 62 | final_translation: string; 63 | review_notes?: string[]; 64 | } 65 | 66 | const server = new McpServer({ 67 | name: "translation-server", 68 | version: "1.0.0", 69 | description: "高精度文本翻译服务器,基于三阶段翻译流程(分析规划、翻译、审校)", 70 | }); 71 | 72 | // 定义翻译工具 73 | server.tool( 74 | "translate_text", 75 | { 76 | text: z.string().describe("需要翻译的源文本"), 77 | target_language: z.string().describe("目标语言代码,例如 'zh'、'en'、'ja'等"), 78 | source_language: z.string().optional().describe("源语言代码,可选参数"), 79 | high_quality: z.boolean().optional().default(true).describe("是否启用高精度翻译流程"), 80 | }, 81 | async ({ text, target_language, source_language, high_quality }) => { 82 | try { 83 | // 启用复合高精度流程或简单翻译 84 | const translation = high_quality 85 | ? await translateTextHighQuality(text, target_language, source_language) 86 | : await translateTextSimple(text, target_language, source_language); 87 | 88 | return { 89 | content: [{ 90 | type: "text", 91 | text: translation 92 | }] 93 | }; 94 | } catch (error) { 95 | const errorMessage = error instanceof Error ? error.message : String(error); 96 | return { 97 | content: [{ 98 | type: "text", 99 | text: `翻译失败: ${errorMessage}` 100 | }], 101 | isError: true 102 | }; 103 | } 104 | } 105 | ); 106 | 107 | // 定义翻译质量评估工具 108 | server.tool( 109 | "evaluate_translation", 110 | { 111 | original_text: z.string().describe("原始文本"), 112 | translated_text: z.string().describe("翻译后的文本"), 113 | detailed_feedback: z.boolean().optional().default(false).describe("是否提供详细反馈"), 114 | }, 115 | async ({ original_text, translated_text, detailed_feedback }) => { 116 | try { 117 | const evaluation = await evaluateTranslationQuality(original_text, translated_text, detailed_feedback); 118 | 119 | return { 120 | content: [{ 121 | type: "text", 122 | text: JSON.stringify(evaluation, null, 2) 123 | }] 124 | }; 125 | } catch (error) { 126 | const errorMessage = error instanceof Error ? error.message : String(error); 127 | return { 128 | content: [{ 129 | type: "text", 130 | text: `评估失败: ${errorMessage}` 131 | }], 132 | isError: true 133 | }; 134 | } 135 | } 136 | ); 137 | 138 | // 修改简单翻译方法,使用OpenAI API 139 | async function translateTextSimple( 140 | text: string, 141 | target_language: string, 142 | source_language?: string 143 | ): Promise { 144 | const baseUrl = process.env.TRANSLATION_BASE_URL; 145 | const apiKey = process.env.TRANSLATION_API_KEY; 146 | const model = process.env.TRANSLATION_MODEL; 147 | 148 | if (!baseUrl || !apiKey || !model) { 149 | throw new Error('缺少翻译API的环境变量配置'); 150 | } 151 | 152 | // 构建提示信息,简洁明了 153 | const systemPrompt = `你是一位专业翻译。请将以下${source_language || "检测到的语言"}文本翻译成${target_language}。 154 | 只输出翻译结果,不要添加解释、原文或其他内容。保持专业、准确、自然的翻译风格。`; 155 | 156 | try { 157 | // 使用OpenAI兼容API进行翻译 158 | const response = await fetch(`${baseUrl}/chat/completions`, { 159 | method: 'POST', 160 | headers: { 161 | 'Content-Type': 'application/json', 162 | 'Authorization': `Bearer ${apiKey}` 163 | }, 164 | body: JSON.stringify({ 165 | model: model, 166 | messages: [ 167 | { role: "system", content: systemPrompt }, 168 | { role: "user", content: text } 169 | ], 170 | temperature: 0.3, // 较低的temperature以获得更一致的翻译 171 | max_tokens: Math.max(1024, text.length * 2), // 动态设置输出长度限制 172 | }), 173 | }); 174 | 175 | if (!response.ok) { 176 | const errorText = await response.text().catch(() => ''); 177 | throw new Error(`OpenAI API请求失败: ${response.status} ${response.statusText} ${errorText}`); 178 | } 179 | 180 | const data = await response.json() as { 181 | choices: [{ 182 | message: { 183 | content: string; 184 | } 185 | }] 186 | }; 187 | 188 | if (!data.choices || !data.choices[0] || !data.choices[0].message.content) { 189 | throw new Error('OpenAI API返回无效响应'); 190 | } 191 | 192 | return data.choices[0].message.content.trim(); 193 | } catch (error) { 194 | console.error('翻译请求失败:', error); 195 | throw error; 196 | } 197 | } 198 | 199 | // 高精度翻译方法(三阶段流程) 200 | async function translateTextHighQuality( 201 | text: string, 202 | target_language: string, 203 | source_language?: string 204 | ): Promise { 205 | console.log(`开始高精度翻译流程,文本长度: ${text.length}字符`); 206 | 207 | // 阶段1:创建翻译规划 208 | const translationPlan = await createTranslationPlan(text, target_language, source_language); 209 | console.log(`阶段1完成:创建翻译规划,内容类型: ${translationPlan.contentType}`); 210 | 211 | // 阶段2:分段翻译 212 | // 将长文本分段,便于更精确的翻译 213 | const segments = segmentText(text); 214 | console.log(`文本已分为${segments.length}个段落`); 215 | 216 | const translatedSegments = []; 217 | for (let i = 0; i < segments.length; i++) { 218 | console.log(`翻译段落 ${i+1}/${segments.length}`); 219 | const translatedSegment = await translateSegment( 220 | segments[i], 221 | translationPlan, 222 | target_language, 223 | source_language 224 | ); 225 | translatedSegments.push(translatedSegment); 226 | } 227 | 228 | // 合并翻译结果 229 | const combinedTranslation = translatedSegments.join('\n\n'); 230 | console.log(`阶段2完成:所有段落翻译完成`); 231 | 232 | // 阶段3:审校翻译 233 | const finalTranslation = await reviewTranslation( 234 | combinedTranslation, 235 | translationPlan, 236 | target_language 237 | ); 238 | console.log(`阶段3完成:翻译审校完成`); 239 | 240 | return finalTranslation; 241 | } 242 | 243 | // 阶段1:创建翻译规划 244 | async function createTranslationPlan( 245 | text: string, 246 | target_language: string, 247 | source_language?: string 248 | ): Promise { 249 | if (!TRANSLATION_BASE_URL || !TRANSLATION_API_KEY || !TRANSLATION_MODEL) { 250 | throw new Error('缺少翻译API的环境变量配置'); 251 | } 252 | 253 | // 构建提示信息,用于创建翻译规划 254 | const systemPrompt = `你是专业翻译分析专家。请分析以下${source_language || ""}文本,创建一个翻译规划用于将其翻译成${target_language}。 255 | 返回一个JSON对象,包含以下字段: 256 | 1. contentType: 文本的类型或体裁(如"技术文档"、"新闻报道"、"科普文章"等) 257 | 2. style: 翻译应采用的风格(如"正式学术"、"通俗易懂"、"简明直接"等) 258 | 3. specializedKnowledge: 文本涉及的专业领域,返回一个字符串数组 259 | 4. keyTerms: 一个对象,包含文本中的关键术语及其对应的${target_language}翻译 260 | 261 | 只返回JSON格式的数据,不要包含任何解释或说明。`; 262 | 263 | try { 264 | // 使用OpenAI兼容API进行翻译规划创建 265 | const response = await fetch(`${TRANSLATION_BASE_URL}/chat/completions`, { 266 | method: 'POST', 267 | headers: { 268 | 'Content-Type': 'application/json', 269 | 'Authorization': `Bearer ${TRANSLATION_API_KEY}` 270 | }, 271 | body: JSON.stringify({ 272 | model: TRANSLATION_MODEL, 273 | messages: [ 274 | { role: "system", content: systemPrompt }, 275 | { role: "user", content: text } 276 | ], 277 | temperature: 0.3, 278 | }), 279 | }); 280 | 281 | if (!response.ok) { 282 | const errorText = await response.text().catch(() => ''); 283 | throw new Error(`创建翻译规划失败: ${response.status} ${response.statusText} ${errorText}`); 284 | } 285 | 286 | const data = await response.json() as { 287 | choices: [{ 288 | message: { 289 | content: string; 290 | } 291 | }] 292 | }; 293 | 294 | if (!data.choices || !data.choices[0] || !data.choices[0].message.content) { 295 | throw new Error('翻译API返回无效响应'); 296 | } 297 | 298 | // 解析JSON响应 299 | const planText = data.choices[0].message.content.trim(); 300 | const planJson = JSON.parse(cleanJsonString(planText)); 301 | 302 | // 确保返回结果符合TranslationPlan类型 303 | return { 304 | contentType: planJson.contentType || "一般文本", 305 | style: planJson.style || "标准", 306 | specializedKnowledge: Array.isArray(planJson.specializedKnowledge) ? planJson.specializedKnowledge : [], 307 | keyTerms: planJson.keyTerms || {} 308 | }; 309 | } catch (error) { 310 | console.error('创建翻译规划失败:', error); 311 | // 返回默认翻译规划,以便流程继续 312 | return { 313 | contentType: "一般文本", 314 | style: "标准", 315 | specializedKnowledge: [], 316 | keyTerms: {} 317 | }; 318 | } 319 | } 320 | 321 | // 阶段2:翻译段落 322 | async function translateSegment( 323 | segment: string, 324 | plan: TranslationPlan, 325 | target_language: string, 326 | source_language?: string 327 | ): Promise { 328 | if (!TRANSLATION_BASE_URL || !TRANSLATION_API_KEY || !TRANSLATION_MODEL) { 329 | throw new Error('缺少翻译API的环境变量配置'); 330 | } 331 | 332 | // 构建关键术语列表 333 | const keyTermsList = Object.entries(plan.keyTerms) 334 | .map(([term, translation]) => `- "${term}": "${translation}"`) 335 | .join('\n'); 336 | 337 | // 构建提示信息,用于段落翻译 338 | const systemPrompt = `你是一位专业${plan.specializedKnowledge.join('、')}领域的翻译专家。请将以下${source_language || ""}文本翻译成${target_language}。 339 | 340 | ## 翻译规划 341 | - 文本类型: ${plan.contentType} 342 | - 风格要求: ${plan.style} 343 | - 专业领域: ${plan.specializedKnowledge.join('、')} 344 | 345 | ## 关键术语表 346 | ${keyTermsList} 347 | 348 | 请遵循以下要求: 349 | 1. 准确传达原文的全部信息和意图 350 | 2. 使用合适的${target_language}表达方式,避免直译 351 | 3. 保持专业领域的术语一致性 352 | 4. 风格符合${plan.style}的要求 353 | 5. 只输出翻译结果,不要添加解释或原文 354 | 355 | ===TRANSLATION===`; 356 | 357 | try { 358 | // 使用OpenAI兼容API进行段落翻译 359 | const response = await fetch(`${TRANSLATION_BASE_URL}/chat/completions`, { 360 | method: 'POST', 361 | headers: { 362 | 'Content-Type': 'application/json', 363 | 'Authorization': `Bearer ${TRANSLATION_API_KEY}` 364 | }, 365 | body: JSON.stringify({ 366 | model: TRANSLATION_MODEL, 367 | messages: [ 368 | { role: "system", content: systemPrompt }, 369 | { role: "user", content: segment } 370 | ], 371 | temperature: 0.3, 372 | max_tokens: Math.max(1024, segment.length * 2), 373 | }), 374 | }); 375 | 376 | if (!response.ok) { 377 | const errorText = await response.text().catch(() => ''); 378 | throw new Error(`翻译段落失败: ${response.status} ${response.statusText} ${errorText}`); 379 | } 380 | 381 | const data = await response.json() as { 382 | choices: [{ 383 | message: { 384 | content: string; 385 | } 386 | }] 387 | }; 388 | 389 | if (!data.choices || !data.choices[0] || !data.choices[0].message.content) { 390 | throw new Error('翻译API返回无效响应'); 391 | } 392 | 393 | const translationResult = data.choices[0].message.content.trim(); 394 | 395 | // 如果返回包含分隔标记,提取实际翻译部分 396 | const parts = translationResult.split("===TRANSLATION==="); 397 | return parts.length > 1 ? parts[1].trim() : translationResult; 398 | } catch (error) { 399 | console.error('翻译段落失败:', error); 400 | return `[翻译错误: ${error instanceof Error ? error.message : String(error)}]`; 401 | } 402 | } 403 | 404 | // 阶段3:审校译文 405 | async function reviewTranslation( 406 | translation: string, 407 | plan: TranslationPlan, 408 | target_language: string 409 | ): Promise { 410 | if (!TRANSLATION_BASE_URL || !TRANSLATION_API_KEY || !TRANSLATION_MODEL) { 411 | throw new Error('缺少翻译API的环境变量配置'); 412 | } 413 | 414 | // 构建关键术语列表 415 | const keyTermsList = Object.entries(plan.keyTerms) 416 | .map(([term, translation]) => `- "${term}": "${translation}"`) 417 | .join('\n'); 418 | 419 | // 构建提示信息,用于审校译文 420 | const systemPrompt = `你是一位专业的${target_language}编辑和校对专家,精通${plan.specializedKnowledge.join('、')}领域。请审校以下${target_language}译文,确保其质量达到专业出版标准。 421 | 422 | ## 翻译规划 423 | - 文本类型: ${plan.contentType} 424 | - 风格要求: ${plan.style} 425 | - 专业领域: ${plan.specializedKnowledge.join('、')} 426 | 427 | ## 关键术语表 428 | ${keyTermsList} 429 | 430 | 请详细审校以下方面: 431 | 1. 术语一致性和准确性 432 | 2. 语法和表达通顺性 433 | 3. 风格与目标读者的匹配度 434 | 4. 专业性和准确性 435 | 436 | 进行必要的修改,然后输出最终的译文版本。在输出最终译文之前,使用===FINAL_TRANSLATION===标记。`; 437 | 438 | try { 439 | // 使用OpenAI兼容API进行审校 440 | const response = await fetch(`${TRANSLATION_BASE_URL}/chat/completions`, { 441 | method: 'POST', 442 | headers: { 443 | 'Content-Type': 'application/json', 444 | 'Authorization': `Bearer ${TRANSLATION_API_KEY}` 445 | }, 446 | body: JSON.stringify({ 447 | model: TRANSLATION_MODEL, 448 | messages: [ 449 | { role: "system", content: systemPrompt }, 450 | { role: "user", content: translation } 451 | ], 452 | temperature: 0.3, 453 | max_tokens: Math.max(1024, translation.length * 2), 454 | }), 455 | }); 456 | 457 | if (!response.ok) { 458 | const errorText = await response.text().catch(() => ''); 459 | throw new Error(`审校译文失败: ${response.status} ${response.statusText} ${errorText}`); 460 | } 461 | 462 | const data = await response.json() as { 463 | choices: [{ 464 | message: { 465 | content: string; 466 | } 467 | }] 468 | }; 469 | 470 | if (!data.choices || !data.choices[0] || !data.choices[0].message.content) { 471 | throw new Error('翻译API返回无效响应'); 472 | } 473 | 474 | const reviewResult = data.choices[0].message.content.trim(); 475 | 476 | // 如果返回包含分隔标记,提取最终翻译部分 477 | const parts = reviewResult.split("===FINAL_TRANSLATION==="); 478 | return parts.length > 1 ? parts[1].trim() : reviewResult; 479 | } catch (error) { 480 | console.error('审校译文失败:', error); 481 | // 如果审校失败,返回原译文 482 | return translation; 483 | } 484 | } 485 | 486 | // 添加支持的语言列表资源 487 | server.resource( 488 | "supported_languages", 489 | "languages://list", 490 | async () => { 491 | return { 492 | contents: [{ 493 | uri: "languages://list", 494 | text: JSON.stringify({ 495 | languages: [ 496 | { code: "zh", name: "中文" }, 497 | { code: "en", name: "英文" }, 498 | { code: "ja", name: "日语" }, 499 | { code: "ko", name: "韩语" }, 500 | { code: "fr", name: "法语" }, 501 | { code: "de", name: "德语" }, 502 | { code: "es", name: "西班牙语" }, 503 | { code: "ru", name: "俄语" }, 504 | { code: "pt", name: "葡萄牙语" }, 505 | { code: "it", name: "意大利语" }, 506 | ] 507 | }, null, 2) 508 | }] 509 | }; 510 | } 511 | ); 512 | 513 | // 在底部的异步函数中替换启动代码 514 | (async function main() { 515 | try { 516 | // 根据服务器模式决定使用哪种传输方式 517 | if (MODE === 'rest') { 518 | // 使用REST传输方式 519 | console.log(`启动REST MCP服务器,端口: ${PORT},端点: ${ENDPOINT}`); 520 | const transport = new RestServerTransport({ 521 | port: Number(PORT), 522 | endpoint: ENDPOINT, 523 | }); 524 | await server.connect(transport); 525 | await transport.startServer(); 526 | 527 | console.log(`MCP REST服务器运行在 http://localhost:${PORT}${ENDPOINT}`); 528 | } else if (MODE === 'http' || MODE === 'sse') { 529 | // 使用HTTP/SSE传输方式 530 | console.log(`启动HTTP/SSE MCP服务器,端口: ${PORT}`); 531 | 532 | const app = express(); 533 | 534 | // 跨域支持 535 | app.use((req: Request, res: Response, next: NextFunction) => { 536 | res.header('Access-Control-Allow-Origin', '*'); 537 | res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); 538 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); 539 | if (req.method === 'OPTIONS') { 540 | res.sendStatus(200); 541 | } else { 542 | next(); 543 | } 544 | }); 545 | 546 | // 会话管理 547 | const transports: {[sessionId: string]: SSEServerTransport} = {}; 548 | 549 | app.get("/sse", (_: Request, res: Response) => { 550 | const transport = new SSEServerTransport('/messages', res); 551 | transports[transport.sessionId] = transport; 552 | 553 | res.on("close", () => { 554 | delete transports[transport.sessionId]; 555 | }); 556 | 557 | server.connect(transport); 558 | }); 559 | 560 | app.post("/messages", (req: Request, res: Response) => { 561 | const sessionId = req.query.sessionId as string; 562 | const transport = transports[sessionId]; 563 | 564 | if (transport) { 565 | transport.handlePostMessage(req, res); 566 | } else { 567 | res.status(400).send('无效的会话ID'); 568 | } 569 | }); 570 | 571 | // 健康检查端点 572 | app.get("/health", (_: Request, res: Response) => { 573 | res.json({ status: 'healthy', version: '0.1.0' }); 574 | }); 575 | 576 | // 启动HTTP服务器 577 | app.listen(Number(PORT), () => { 578 | console.log(`MCP HTTP服务器运行在 http://localhost:${PORT}`); 579 | console.log(`- SSE端点: http://localhost:${PORT}/sse`); 580 | console.log(`- 消息端点: http://localhost:${PORT}/messages`); 581 | }); 582 | } else { 583 | // 使用标准输入/输出传输方式 (默认模式或指定为stdio) 584 | console.log("启动标准输入/输出MCP服务器"); 585 | const transport = new StdioServerTransport(); 586 | await server.connect(transport); 587 | console.log("翻译服务器已启动,使用标准输入/输出通信"); 588 | } 589 | } catch (error) { 590 | console.error("启动翻译服务器失败:", error); 591 | process.exit(1); 592 | } 593 | })(); -------------------------------------------------------------------------------- /src/text_eval.ts: -------------------------------------------------------------------------------- 1 | import { cleanJsonString, segmentText } from "./utils.js"; 2 | 3 | // 定义翻译质量评估中的问题类型接口 4 | export interface TranslationIssue { 5 | type: string; 6 | description: string; 7 | originalText: string; 8 | translatedText: string; 9 | suggestion: string; 10 | start: number; 11 | end: number; 12 | reason: string; 13 | } 14 | 15 | // 定义翻译质量评估中的段落反馈接口 16 | export interface TranslationSegmentFeedback { 17 | segmentIndex: number; 18 | issues: TranslationIssue[]; 19 | } 20 | 21 | // 定义翻译质量评估响应接口 22 | export interface TranslationEvaluationResponse { 23 | score: number; 24 | comments: string[]; 25 | segmentScores: number[]; 26 | segmentFeedbacks: TranslationSegmentFeedback[]; 27 | originalSegments?: string[]; 28 | translatedSegments?: string[]; 29 | } 30 | 31 | // 评估翻译质量的函数 32 | export async function evaluateTranslationQuality( 33 | originalText: string, 34 | translatedText: string, 35 | detailedFeedback: boolean = false 36 | ): Promise { 37 | const baseUrl = process.env.TRANSLATION_BASE_URL; 38 | const apiKey = process.env.TRANSLATION_API_KEY; 39 | const model = process.env.TRANSLATION_MODEL; 40 | 41 | if (!baseUrl || !apiKey || !model) { 42 | throw new Error('缺少翻译API的环境变量配置'); 43 | } 44 | 45 | // 构建评估提示词 46 | const prompt = ` 47 | 你是专业的翻译质量评估专家,请对以下翻译进行质量评估: 48 | 49 | 原文: 50 | \`\`\` 51 | ${originalText} 52 | \`\`\` 53 | 54 | 请你首先阅读上面的原文,制订一个评估翻译质量的策略,然后对比下面的译文: 55 | 56 | 译文: 57 | \`\`\` 58 | ${translatedText} 59 | \`\`\` 60 | 61 | 请从以下几个方面进行评估: 62 | 1. 准确性:译文是否准确传达了原文的意思 63 | 2. 流畅性:译文是否符合目标语言表达习惯 64 | 3. 术语使用:专业术语的翻译是否准确 65 | 4. 风格一致性:译文是否保持了原文的风格 66 | 67 | 请按照以下格式返回评估结果: 68 | { 69 | "score": 评分(0-100分), 70 | "comments": [ 71 | "具体评价1", 72 | "具体评价2", 73 | ... 74 | ], 75 | "segmentScores": [ 76 | 段落1评分, 77 | 段落2评分, 78 | ... 79 | ], 80 | "segmentFeedbacks": [ 81 | { 82 | "segmentIndex": 段落索引, 83 | "issues": [ 84 | { 85 | "type": "问题类型", 86 | "description": "问题描述", 87 | "originalText": "原文片段", 88 | "translatedText": "译文片段", 89 | "suggestion": "建议的翻译结果(直接输出修改后的文本,不要包含'改为'等指示词)", 90 | "start": 起始位置, 91 | "end": 结束位置, 92 | "reason": "修改理由" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | 99 | 请确保返回的是有效的JSON格式。 100 | ${!detailedFeedback ? '请保持评估简洁,不需要详细分析每个问题。' : '请提供详细的问题分析和具体改进建议。'} 101 | `; 102 | 103 | const messages = [ 104 | { role: 'system', content: '你是一个专业的翻译质量评估专家。请严格按照要求的JSON格式返回评估结果。' }, 105 | { role: 'user', content: prompt } 106 | ]; 107 | 108 | try { 109 | // 调用 API 110 | const response = await fetch(`${baseUrl}/chat/completions`, { 111 | method: 'POST', 112 | headers: { 113 | 'Authorization': `Bearer ${apiKey}`, 114 | 'Content-Type': 'application/json', 115 | }, 116 | body: JSON.stringify({ 117 | model: model, 118 | messages, 119 | temperature: 0.3, // 降低随机性,使评估更稳定 120 | }), 121 | }); 122 | 123 | if (!response.ok) { 124 | const errorData = await response.json() as any; 125 | throw new Error(`API 请求失败: ${errorData.error?.message || response.statusText}`); 126 | } 127 | 128 | const data = await response.json() as { 129 | choices: [{ 130 | message: { 131 | content: string; 132 | } 133 | }] 134 | }; 135 | 136 | const result = data.choices[0]?.message?.content || ''; 137 | 138 | try { 139 | // 处理可能存在的 Markdown 代码块标记 140 | let jsonContent = result; 141 | if (result.includes('```json')) { 142 | jsonContent = result.split('```json')[1].split('```')[0].trim(); 143 | } else if (result.includes('```')) { 144 | jsonContent = result.split('```')[1].split('```')[0].trim(); 145 | } 146 | 147 | console.log('处理后的评估内容:', jsonContent); 148 | const evaluationResult = JSON.parse(cleanJsonString(jsonContent)); 149 | 150 | // 验证必要的字段 151 | if (!evaluationResult.score || !evaluationResult.comments || !evaluationResult.segmentScores) { 152 | throw new Error('评估结果缺少必要字段'); 153 | } 154 | 155 | // 分割文本为段落 156 | const originalSegments = segmentText(originalText); 157 | const translatedSegments = segmentText(translatedText); 158 | 159 | // 构建并返回结果 160 | return { 161 | ...evaluationResult, 162 | originalSegments: originalSegments.map((segment, index) => `[${index + 1}] ${segment.trim()}`), 163 | translatedSegments: translatedSegments.map((segment, index) => `[${index + 1}] ${segment.trim()}`) 164 | }; 165 | } catch (error) { 166 | console.error('解析评估结果失败:', error); 167 | console.error('原始内容:', result); 168 | throw new Error(`解析评估结果失败: ${error instanceof Error ? error.message : String(error)}`); 169 | } 170 | } catch (error) { 171 | console.error('翻译质量评估失败:', error); 172 | throw new Error(`翻译质量评估失败: ${error instanceof Error ? error.message : String(error)}`); 173 | } 174 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 清理可能包含Markdown格式的JSON字符串 3 | * @param jsonString 可能包含Markdown格式的JSON字符串 4 | * @returns 清理后的JSON字符串 5 | */ 6 | export function cleanJsonString(jsonString: string): string { 7 | // 移除可能的Markdown代码块标记 8 | let cleaned = jsonString.trim(); 9 | 10 | // 移除开头的```json、```、或其他代码块标记 11 | cleaned = cleaned.replace(/^```(\w*\n|\n)?/, ''); 12 | 13 | // 移除结尾的``` 14 | cleaned = cleaned.replace(/```$/, ''); 15 | 16 | // 移除可能的注释 17 | cleaned = cleaned.replace(/\/\/.*/g, ''); 18 | 19 | return cleaned.trim(); 20 | } 21 | 22 | // 文本分段 23 | export function segmentText(text: string): string[] { 24 | // 按段落分割(空行分隔) 25 | const segments = text.split(/\n\s*\n/).filter(segment => segment.trim().length > 0); 26 | 27 | // 如果没有段落分隔,或者只有一个段落,可以考虑按句子分割 28 | if (segments.length <= 1 && text.length > 500) { 29 | return text.split(/(?<=[.!?。!?])\s+/).filter(segment => segment.trim().length > 0); 30 | } 31 | 32 | return segments; 33 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "outDir": "./dist", 11 | "declaration": true, 12 | "sourceMap": true, 13 | "resolveJsonModule": true 14 | }, 15 | "include": [ 16 | "src/**/*" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "dist" 21 | ] 22 | } --------------------------------------------------------------------------------