ppt-translator-mcp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/README.md +118 -0
- package/amazon-q-config-example.json +19 -0
- package/index.js +110 -0
- package/package.json +32 -0
- package/publish.sh +22 -0
- package/requirements.txt +3 -0
- package/server.py +565 -0
- package/test_local.sh +42 -0
package/.env.example
ADDED
package/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# PowerPoint Translator MCP Service
|
2
|
+
|
3
|
+
A Model Context Protocol (MCP) service that provides PowerPoint translation capabilities using AWS Bedrock models.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Translate PowerPoint presentations to multiple languages
|
8
|
+
- Preserve formatting during translation
|
9
|
+
- Support for multiple translation engines (Nova Lite and Claude)
|
10
|
+
- Intelligent handling of proper nouns, brand names, and special content
|
11
|
+
|
12
|
+
## Supported Languages
|
13
|
+
|
14
|
+
- Simplified Chinese (zh-CN)
|
15
|
+
- Traditional Chinese (zh-TW)
|
16
|
+
- English (en)
|
17
|
+
- Japanese (ja)
|
18
|
+
- Korean (ko)
|
19
|
+
- French (fr)
|
20
|
+
- German (de)
|
21
|
+
- Spanish (es)
|
22
|
+
|
23
|
+
## Prerequisites
|
24
|
+
|
25
|
+
- Node.js 14+ (for npm package installation)
|
26
|
+
- Python 3.8+
|
27
|
+
- AWS account with Bedrock access
|
28
|
+
- AWS credentials configured
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
### Using npm
|
33
|
+
|
34
|
+
```bash
|
35
|
+
npm install -g ppt-translator-mcp-zhu2mu-unique
|
36
|
+
```
|
37
|
+
|
38
|
+
### Using Amazon Q Configuration
|
39
|
+
|
40
|
+
Add the following to your Amazon Q configuration:
|
41
|
+
|
42
|
+
```json
|
43
|
+
"mcpServers": {
|
44
|
+
"ppt-translator": {
|
45
|
+
"timeout": 60,
|
46
|
+
"type": "stdio",
|
47
|
+
"command": "npx",
|
48
|
+
"args": [
|
49
|
+
"-y",
|
50
|
+
"ppt-translator-mcp-zhu2mu-unique@latest"
|
51
|
+
],
|
52
|
+
"env": {
|
53
|
+
"AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}",
|
54
|
+
"AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}",
|
55
|
+
"AWS_REGION": "us-east-1",
|
56
|
+
"DEFAULT_TARGET_LANGUAGE": "zh-CN"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
|
64
|
+
Once the MCP server is running, you can use it with any MCP-compatible client like Amazon Q or Claude Desktop.
|
65
|
+
|
66
|
+
### Available Tools
|
67
|
+
|
68
|
+
1. `translate_ppt` - Translate a PowerPoint document
|
69
|
+
- Parameters:
|
70
|
+
- `input_file`: Path to the input PowerPoint file (required)
|
71
|
+
- `target_language`: Target language code (default: zh-CN)
|
72
|
+
- `output_file`: Path to save the translated file (optional)
|
73
|
+
- `translation_method`: Translation method, 'nova' or 'claude' (default: nova)
|
74
|
+
|
75
|
+
2. `list_supported_languages` - List all supported target languages
|
76
|
+
|
77
|
+
## Example
|
78
|
+
|
79
|
+
```
|
80
|
+
Translate my presentation.pptx to Japanese using the Claude model
|
81
|
+
```
|
82
|
+
|
83
|
+
## Development
|
84
|
+
|
85
|
+
### Local Development
|
86
|
+
|
87
|
+
1. Clone this repository:
|
88
|
+
```bash
|
89
|
+
git clone https://github.com/yourusername/mcpppttranslator.git
|
90
|
+
cd mcpppttranslator
|
91
|
+
```
|
92
|
+
|
93
|
+
2. Install dependencies:
|
94
|
+
```bash
|
95
|
+
npm install
|
96
|
+
pip install -r requirements.txt
|
97
|
+
```
|
98
|
+
|
99
|
+
3. Run the server:
|
100
|
+
```bash
|
101
|
+
node index.js
|
102
|
+
```
|
103
|
+
|
104
|
+
### Publishing to npm
|
105
|
+
|
106
|
+
1. Update version in package.json
|
107
|
+
2. Login to npm:
|
108
|
+
```bash
|
109
|
+
npm login
|
110
|
+
```
|
111
|
+
3. Publish:
|
112
|
+
```bash
|
113
|
+
npm publish --access public
|
114
|
+
```
|
115
|
+
|
116
|
+
## License
|
117
|
+
|
118
|
+
MIT
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"mcpServers": {
|
3
|
+
"ppt-translator": {
|
4
|
+
"timeout": 60,
|
5
|
+
"type": "stdio",
|
6
|
+
"command": "npx",
|
7
|
+
"args": [
|
8
|
+
"-y",
|
9
|
+
"ppt-translator-mcp@latest"
|
10
|
+
],
|
11
|
+
"env": {
|
12
|
+
"AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}",
|
13
|
+
"AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}",
|
14
|
+
"AWS_REGION": "us-east-1",
|
15
|
+
"DEFAULT_TARGET_LANGUAGE": "zh-CN"
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
package/index.js
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
const { spawn } = require('child_process');
|
4
|
+
const path = require('path');
|
5
|
+
const fs = require('fs');
|
6
|
+
|
7
|
+
// Ensure Python and pip are installed
|
8
|
+
function checkPythonDependencies() {
|
9
|
+
try {
|
10
|
+
// Check if Python is available
|
11
|
+
const pythonVersion = spawn('python3', ['--version']);
|
12
|
+
pythonVersion.on('error', (err) => {
|
13
|
+
console.error('Python3 is not installed or not in PATH. Please install Python 3.8+');
|
14
|
+
process.exit(1);
|
15
|
+
});
|
16
|
+
|
17
|
+
// Check if pip is available
|
18
|
+
const pipVersion = spawn('pip3', ['--version']);
|
19
|
+
pipVersion.on('error', (err) => {
|
20
|
+
console.error('pip3 is not installed or not in PATH. Please install pip');
|
21
|
+
process.exit(1);
|
22
|
+
});
|
23
|
+
} catch (error) {
|
24
|
+
console.error('Error checking Python dependencies:', error);
|
25
|
+
process.exit(1);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
// Install required Python packages
|
30
|
+
function installPythonDependencies() {
|
31
|
+
return new Promise((resolve, reject) => {
|
32
|
+
console.log('Installing Python dependencies...');
|
33
|
+
|
34
|
+
// Path to requirements.txt in the package
|
35
|
+
const requirementsPath = path.join(__dirname, 'requirements.txt');
|
36
|
+
|
37
|
+
// Install dependencies using pip
|
38
|
+
const pip = spawn('pip3', ['install', '-r', requirementsPath]);
|
39
|
+
|
40
|
+
pip.stdout.on('data', (data) => {
|
41
|
+
console.log(`${data}`);
|
42
|
+
});
|
43
|
+
|
44
|
+
pip.stderr.on('data', (data) => {
|
45
|
+
console.error(`${data}`);
|
46
|
+
});
|
47
|
+
|
48
|
+
pip.on('close', (code) => {
|
49
|
+
if (code === 0) {
|
50
|
+
console.log('Python dependencies installed successfully');
|
51
|
+
resolve();
|
52
|
+
} else {
|
53
|
+
console.error(`pip exited with code ${code}`);
|
54
|
+
reject(new Error(`Failed to install Python dependencies (exit code: ${code})`));
|
55
|
+
}
|
56
|
+
});
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
// Run the MCP server
|
61
|
+
function runMCPServer() {
|
62
|
+
console.log('Starting PowerPoint Translator MCP server...');
|
63
|
+
|
64
|
+
// Path to the Python server script
|
65
|
+
const serverPath = path.join(__dirname, 'server.py');
|
66
|
+
|
67
|
+
// Make sure the script is executable
|
68
|
+
fs.chmodSync(serverPath, '755');
|
69
|
+
|
70
|
+
// Run the Python script in MCP mode
|
71
|
+
const server = spawn('python3', [serverPath]);
|
72
|
+
|
73
|
+
// Pipe stdin/stdout directly to the Python process for MCP communication
|
74
|
+
process.stdin.pipe(server.stdin);
|
75
|
+
server.stdout.pipe(process.stdout);
|
76
|
+
server.stderr.on('data', (data) => {
|
77
|
+
process.stderr.write(`${data}`);
|
78
|
+
});
|
79
|
+
|
80
|
+
server.on('close', (code) => {
|
81
|
+
console.log(`MCP server exited with code ${code}`);
|
82
|
+
process.exit(code);
|
83
|
+
});
|
84
|
+
|
85
|
+
// Handle process termination
|
86
|
+
process.on('SIGINT', () => {
|
87
|
+
console.log('Received SIGINT. Shutting down MCP server...');
|
88
|
+
server.kill('SIGINT');
|
89
|
+
});
|
90
|
+
|
91
|
+
process.on('SIGTERM', () => {
|
92
|
+
console.log('Received SIGTERM. Shutting down MCP server...');
|
93
|
+
server.kill('SIGTERM');
|
94
|
+
});
|
95
|
+
}
|
96
|
+
|
97
|
+
// Main function
|
98
|
+
async function main() {
|
99
|
+
try {
|
100
|
+
checkPythonDependencies();
|
101
|
+
await installPythonDependencies();
|
102
|
+
runMCPServer();
|
103
|
+
} catch (error) {
|
104
|
+
console.error('Error starting MCP server:', error);
|
105
|
+
process.exit(1);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
// Run the main function
|
110
|
+
main();
|
package/package.json
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"name": "ppt-translator-mcp",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "MCP server for PowerPoint translation using AWS Bedrock",
|
5
|
+
"main": "index.js",
|
6
|
+
"bin": {
|
7
|
+
"mcp-ppt-translator": "./index.js"
|
8
|
+
},
|
9
|
+
"scripts": {
|
10
|
+
"start": "node index.js"
|
11
|
+
},
|
12
|
+
"keywords": [
|
13
|
+
"mcp",
|
14
|
+
"powerpoint",
|
15
|
+
"translation",
|
16
|
+
"aws",
|
17
|
+
"bedrock"
|
18
|
+
],
|
19
|
+
"author": "zhuermu",
|
20
|
+
"license": "MIT",
|
21
|
+
"repository": {
|
22
|
+
"type": "git",
|
23
|
+
"url": "git+https://github.com/zhuermu/mcpppttranslator.git"
|
24
|
+
},
|
25
|
+
"bugs": {
|
26
|
+
"url": "https://github.com/zhuermu/mcpppttranslator/issues"
|
27
|
+
},
|
28
|
+
"homepage": "https://github.com/zhuermu/mcpppttranslator#readme",
|
29
|
+
"dependencies": {
|
30
|
+
"@modelcontextprotocol/server": "^1.0.0"
|
31
|
+
}
|
32
|
+
}
|
package/publish.sh
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# 检查是否提供了NPM令牌
|
4
|
+
if [ -z "$1" ]; then
|
5
|
+
echo "请提供NPM令牌作为参数"
|
6
|
+
echo "用法: ./publish.sh your-npm-token"
|
7
|
+
exit 1
|
8
|
+
fi
|
9
|
+
|
10
|
+
# 设置NPM令牌
|
11
|
+
NPM_TOKEN=$1
|
12
|
+
|
13
|
+
# 创建临时.npmrc文件
|
14
|
+
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
|
15
|
+
|
16
|
+
# 发布包
|
17
|
+
npm publish --access public
|
18
|
+
|
19
|
+
# 删除临时.npmrc文件
|
20
|
+
rm .npmrc
|
21
|
+
|
22
|
+
echo "发布完成!"
|
package/requirements.txt
ADDED
package/server.py
ADDED
@@ -0,0 +1,565 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
PPT Translator Server
|
4
|
+
A server that provides PowerPoint translation capabilities using AWS Bedrock models.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
import sys
|
9
|
+
import logging
|
10
|
+
import json
|
11
|
+
import argparse
|
12
|
+
from typing import Dict, Any, Optional, List
|
13
|
+
from pathlib import Path
|
14
|
+
|
15
|
+
try:
|
16
|
+
from pptx import Presentation
|
17
|
+
except ImportError:
|
18
|
+
print("Error: python-pptx not found. Please install it with 'pip install python-pptx'")
|
19
|
+
sys.exit(1)
|
20
|
+
|
21
|
+
try:
|
22
|
+
import boto3
|
23
|
+
except ImportError:
|
24
|
+
print("Error: boto3 not found. Please install it with 'pip install boto3'")
|
25
|
+
sys.exit(1)
|
26
|
+
|
27
|
+
try:
|
28
|
+
from dotenv import load_dotenv
|
29
|
+
except ImportError:
|
30
|
+
print("Error: python-dotenv not found. Please install it with 'pip install python-dotenv'")
|
31
|
+
sys.exit(1)
|
32
|
+
|
33
|
+
# Load environment variables
|
34
|
+
load_dotenv()
|
35
|
+
|
36
|
+
# Configure logging
|
37
|
+
logging.basicConfig(level=logging.INFO)
|
38
|
+
logger = logging.getLogger(__name__)
|
39
|
+
|
40
|
+
# Initialize AWS Bedrock client
|
41
|
+
try:
|
42
|
+
bedrock_client = boto3.client(
|
43
|
+
'bedrock-runtime',
|
44
|
+
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
|
45
|
+
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
|
46
|
+
region_name=os.getenv('AWS_REGION', 'us-east-1')
|
47
|
+
)
|
48
|
+
except Exception as e:
|
49
|
+
logger.error(f"Failed to initialize AWS Bedrock client: {str(e)}")
|
50
|
+
print(f"Error: Failed to initialize AWS Bedrock client. Please check your AWS credentials.")
|
51
|
+
sys.exit(1)
|
52
|
+
|
53
|
+
default_target_language = os.getenv('DEFAULT_TARGET_LANGUAGE', 'zh-CN')
|
54
|
+
|
55
|
+
def _translate_text(text: str, target_language: str, method: str) -> Optional[str]:
|
56
|
+
"""Translate a single text"""
|
57
|
+
try:
|
58
|
+
if method == 'claude':
|
59
|
+
return _translate_with_claude(text, target_language)
|
60
|
+
else:
|
61
|
+
return _translate_with_nova(text, target_language)
|
62
|
+
except Exception as e:
|
63
|
+
logger.error(f"Failed to translate text: {str(e)}")
|
64
|
+
return None
|
65
|
+
|
66
|
+
def _translate_with_claude(text: str, target_language: str) -> str:
|
67
|
+
"""Translate using AWS Bedrock Claude 3.5 Sonnet"""
|
68
|
+
language_map = {
|
69
|
+
'zh-CN': '简体中文',
|
70
|
+
'zh-TW': '繁体中文',
|
71
|
+
'en': '英语',
|
72
|
+
'ja': '日语',
|
73
|
+
'ko': '韩语',
|
74
|
+
'fr': '法语',
|
75
|
+
'de': '德语',
|
76
|
+
'es': '西班牙语'
|
77
|
+
}
|
78
|
+
|
79
|
+
target_lang_name = language_map.get(target_language, target_language)
|
80
|
+
|
81
|
+
body = json.dumps({
|
82
|
+
"anthropic_version": "bedrock-2023-05-31",
|
83
|
+
"max_tokens": 1000,
|
84
|
+
"messages": [
|
85
|
+
{
|
86
|
+
"role": "user",
|
87
|
+
"content": f"Please translate the following text to {target_lang_name}. Keep proper nouns, brand names, person names, place names, company names, and currencies untranslated. Only return the translation result:\n\n{text}"
|
88
|
+
}
|
89
|
+
]
|
90
|
+
})
|
91
|
+
|
92
|
+
response = bedrock_client.invoke_model(
|
93
|
+
body=body,
|
94
|
+
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
|
95
|
+
accept="application/json",
|
96
|
+
contentType="application/json"
|
97
|
+
)
|
98
|
+
|
99
|
+
response_body = json.loads(response.get('body').read())
|
100
|
+
return response_body['content'][0]['text'].strip()
|
101
|
+
|
102
|
+
def _translate_with_nova(text: str, target_language: str) -> str:
|
103
|
+
"""Translate using AWS Bedrock Nova Lite"""
|
104
|
+
language_map = {
|
105
|
+
'zh-CN': '简体中文',
|
106
|
+
'zh-TW': '繁体中文',
|
107
|
+
'en': '英语',
|
108
|
+
'ja': '日语',
|
109
|
+
'ko': '韩语',
|
110
|
+
'fr': '法语',
|
111
|
+
'de': '德语',
|
112
|
+
'es': '西班牙语'
|
113
|
+
}
|
114
|
+
|
115
|
+
target_lang_name = language_map.get(target_language, target_language)
|
116
|
+
|
117
|
+
# Check if it's a proper noun or number
|
118
|
+
if text.strip().isdigit() or (len(text.strip()) <= 3 and not any(c.isalpha() for c in text)):
|
119
|
+
return text
|
120
|
+
|
121
|
+
body = json.dumps({
|
122
|
+
"messages": [
|
123
|
+
{
|
124
|
+
"role": "user",
|
125
|
+
"content": [
|
126
|
+
{
|
127
|
+
"text": f"""Translate the following text to {target_lang_name}.
|
128
|
+
|
129
|
+
Please follow these rules:
|
130
|
+
1. Keep all brand names untranslated, such as Amazon, AWS, Bedrock, Nova, etc.
|
131
|
+
2. Keep all person names untranslated
|
132
|
+
3. Keep all company names untranslated
|
133
|
+
4. Keep all product names untranslated
|
134
|
+
5. Keep all currency amounts untranslated
|
135
|
+
6. For content that shouldn't be translated, return the original text
|
136
|
+
7. Only return the translation result, without any explanations or original text
|
137
|
+
|
138
|
+
Original text: {text}"""
|
139
|
+
}
|
140
|
+
]
|
141
|
+
}
|
142
|
+
],
|
143
|
+
"inferenceConfig": {
|
144
|
+
"max_new_tokens": 1000,
|
145
|
+
"temperature": 0.1
|
146
|
+
}
|
147
|
+
})
|
148
|
+
|
149
|
+
response = bedrock_client.invoke_model(
|
150
|
+
body=body,
|
151
|
+
modelId="amazon.nova-lite-v1:0",
|
152
|
+
accept="application/json",
|
153
|
+
contentType="application/json"
|
154
|
+
)
|
155
|
+
|
156
|
+
response_body = json.loads(response.get('body').read())
|
157
|
+
translated_text = response_body['output']['message']['content'][0]['text'].strip()
|
158
|
+
|
159
|
+
# If the translation result contains "important rules" or "don't translate", return the original text
|
160
|
+
if "重要规则" in translated_text or "不要翻译" in translated_text:
|
161
|
+
return text
|
162
|
+
|
163
|
+
return translated_text
|
164
|
+
|
165
|
+
def _translate_ppt(input_file: str, output_file: str, target_language: str, method: str) -> Dict[str, Any]:
|
166
|
+
"""Translate a PowerPoint file"""
|
167
|
+
try:
|
168
|
+
# Load the PPT
|
169
|
+
prs = Presentation(input_file)
|
170
|
+
translated_count = 0
|
171
|
+
|
172
|
+
# Iterate through all slides
|
173
|
+
for slide_idx, slide in enumerate(prs.slides):
|
174
|
+
logger.info(f"Processing slide {slide_idx + 1}")
|
175
|
+
|
176
|
+
# Iterate through all shapes in the slide
|
177
|
+
for shape in slide.shapes:
|
178
|
+
try:
|
179
|
+
if hasattr(shape, "text") and shape.text.strip():
|
180
|
+
original_text = shape.text.strip()
|
181
|
+
|
182
|
+
# Translate the text
|
183
|
+
translated_text = _translate_text(original_text, target_language, method)
|
184
|
+
|
185
|
+
if translated_text and translated_text != original_text:
|
186
|
+
# Update the text while preserving formatting
|
187
|
+
if hasattr(shape, 'text_frame') and shape.text_frame:
|
188
|
+
_update_text_frame_with_formatting(shape.text_frame, translated_text)
|
189
|
+
else:
|
190
|
+
shape.text = translated_text
|
191
|
+
|
192
|
+
translated_count += 1
|
193
|
+
logger.info(f"Translated: '{original_text[:50]}...' -> '{translated_text[:50]}...'")
|
194
|
+
except Exception as e:
|
195
|
+
logger.error(f"Error processing shape: {str(e)}")
|
196
|
+
continue
|
197
|
+
|
198
|
+
# Save the translated PPT
|
199
|
+
prs.save(output_file)
|
200
|
+
logger.info(f"Translation completed, saved to: {output_file}")
|
201
|
+
|
202
|
+
return {"translated_count": translated_count}
|
203
|
+
except Exception as e:
|
204
|
+
logger.error(f"Error translating PPT: {str(e)}")
|
205
|
+
raise
|
206
|
+
|
207
|
+
def _update_text_frame_with_formatting(text_frame, new_text):
|
208
|
+
"""Update text frame content while preserving original formatting"""
|
209
|
+
try:
|
210
|
+
if not text_frame.paragraphs:
|
211
|
+
return
|
212
|
+
|
213
|
+
# Save the format of the first paragraph
|
214
|
+
first_paragraph = text_frame.paragraphs[0]
|
215
|
+
if first_paragraph.runs:
|
216
|
+
# Save the format of the first run
|
217
|
+
first_run = first_paragraph.runs[0]
|
218
|
+
font_name = first_run.font.name
|
219
|
+
font_size = first_run.font.size
|
220
|
+
font_bold = first_run.font.bold
|
221
|
+
font_italic = first_run.font.italic
|
222
|
+
|
223
|
+
# Default to black
|
224
|
+
from pptx.dml.color import RGBColor
|
225
|
+
font_color = RGBColor(0, 0, 0) # Default black
|
226
|
+
|
227
|
+
# Safely get the color
|
228
|
+
try:
|
229
|
+
if hasattr(first_run.font, 'color'):
|
230
|
+
if hasattr(first_run.font.color, 'rgb') and first_run.font.color.rgb:
|
231
|
+
font_color = first_run.font.color.rgb
|
232
|
+
elif hasattr(first_run.font.color, 'type'):
|
233
|
+
# If it's a theme color, we still use default black
|
234
|
+
pass
|
235
|
+
except Exception as e:
|
236
|
+
logger.error(f"Error getting font color: {str(e)}")
|
237
|
+
|
238
|
+
# Clear all paragraphs
|
239
|
+
text_frame.clear()
|
240
|
+
|
241
|
+
# Add new text and apply original formatting
|
242
|
+
paragraph = text_frame.paragraphs[0]
|
243
|
+
# The correct method is to call add_run() directly on the paragraph, not on the runs collection
|
244
|
+
run = paragraph.add_run()
|
245
|
+
run.text = new_text
|
246
|
+
|
247
|
+
# Restore formatting
|
248
|
+
if font_name:
|
249
|
+
run.font.name = font_name
|
250
|
+
if font_size:
|
251
|
+
run.font.size = font_size
|
252
|
+
if font_bold is not None:
|
253
|
+
run.font.bold = font_bold
|
254
|
+
if font_italic is not None:
|
255
|
+
run.font.italic = font_italic
|
256
|
+
|
257
|
+
# Set font color
|
258
|
+
run.font.color.rgb = font_color
|
259
|
+
else:
|
260
|
+
# If there are no runs, set the text directly
|
261
|
+
text_frame.text = new_text
|
262
|
+
except Exception as e:
|
263
|
+
logger.error(f"Error updating text formatting: {str(e)}")
|
264
|
+
# If formatting fails, set the text directly
|
265
|
+
text_frame.text = new_text
|
266
|
+
|
267
|
+
def translate_ppt(
|
268
|
+
input_file: str,
|
269
|
+
target_language: str = default_target_language,
|
270
|
+
output_file: str = None,
|
271
|
+
translation_method: str = "nova"
|
272
|
+
) -> Dict[str, Any]:
|
273
|
+
"""Translate a PowerPoint document to the specified language
|
274
|
+
|
275
|
+
Args:
|
276
|
+
input_file: Path to the input PowerPoint file
|
277
|
+
target_language: Target language code, default is zh-CN
|
278
|
+
output_file: Path to save the translated file, if not provided it will be auto-generated
|
279
|
+
translation_method: Translation method, can be 'nova' or 'claude'
|
280
|
+
|
281
|
+
Returns:
|
282
|
+
Dict[str, Any]: Translation result information
|
283
|
+
"""
|
284
|
+
try:
|
285
|
+
if not input_file:
|
286
|
+
return {"error": "No input file path provided"}
|
287
|
+
|
288
|
+
if not Path(input_file).exists():
|
289
|
+
return {"error": f"File does not exist: {input_file}"}
|
290
|
+
|
291
|
+
# Generate output filename
|
292
|
+
if not output_file:
|
293
|
+
input_path = Path(input_file)
|
294
|
+
output_file = str(input_path.parent / f"{input_path.stem}_translated_{target_language}{input_path.suffix}")
|
295
|
+
|
296
|
+
# Execute translation
|
297
|
+
result = _translate_ppt(input_file, output_file, target_language, translation_method)
|
298
|
+
|
299
|
+
return {
|
300
|
+
"success": True,
|
301
|
+
"input_file": input_file,
|
302
|
+
"output_file": output_file,
|
303
|
+
"target_language": target_language,
|
304
|
+
"translation_method": translation_method,
|
305
|
+
"translated_texts_count": result.get('translated_count', 0),
|
306
|
+
"message": "PowerPoint translation completed"
|
307
|
+
}
|
308
|
+
|
309
|
+
except Exception as e:
|
310
|
+
logger.error(f"Error processing request: {str(e)}")
|
311
|
+
return {"error": f"Processing failed: {str(e)}"}
|
312
|
+
|
313
|
+
def list_supported_languages() -> Dict[str, str]:
|
314
|
+
"""List all supported target languages for translation
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
Dict[str, str]: Dictionary of language codes and their names
|
318
|
+
"""
|
319
|
+
return {
|
320
|
+
"zh-CN": "Simplified Chinese",
|
321
|
+
"zh-TW": "Traditional Chinese",
|
322
|
+
"en": "English",
|
323
|
+
"ja": "Japanese",
|
324
|
+
"ko": "Korean",
|
325
|
+
"fr": "French",
|
326
|
+
"de": "German",
|
327
|
+
"es": "Spanish"
|
328
|
+
}
|
329
|
+
|
330
|
+
# MCP protocol implementation
|
331
|
+
def handle_mcp_request():
|
332
|
+
"""Handle MCP protocol requests"""
|
333
|
+
try:
|
334
|
+
# Read request from stdin
|
335
|
+
request_line = sys.stdin.readline().strip()
|
336
|
+
request = json.loads(request_line)
|
337
|
+
|
338
|
+
# Process the request
|
339
|
+
if request.get("type") == "invoke":
|
340
|
+
tool_name = request.get("name")
|
341
|
+
params = request.get("params", {})
|
342
|
+
|
343
|
+
if tool_name == "translate_ppt":
|
344
|
+
result = translate_ppt(
|
345
|
+
input_file=params.get("input_file"),
|
346
|
+
target_language=params.get("target_language", default_target_language),
|
347
|
+
output_file=params.get("output_file"),
|
348
|
+
translation_method=params.get("translation_method", "nova")
|
349
|
+
)
|
350
|
+
|
351
|
+
# Send response
|
352
|
+
response = {
|
353
|
+
"type": "result",
|
354
|
+
"result": result
|
355
|
+
}
|
356
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
357
|
+
sys.stdout.flush()
|
358
|
+
|
359
|
+
elif tool_name == "list_supported_languages":
|
360
|
+
result = list_supported_languages()
|
361
|
+
|
362
|
+
# Send response
|
363
|
+
response = {
|
364
|
+
"type": "result",
|
365
|
+
"result": result
|
366
|
+
}
|
367
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
368
|
+
sys.stdout.flush()
|
369
|
+
|
370
|
+
else:
|
371
|
+
# Unknown tool
|
372
|
+
response = {
|
373
|
+
"type": "error",
|
374
|
+
"error": f"Unknown tool: {tool_name}"
|
375
|
+
}
|
376
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
377
|
+
sys.stdout.flush()
|
378
|
+
|
379
|
+
elif request.get("type") == "describe":
|
380
|
+
# Send server description
|
381
|
+
response = {
|
382
|
+
"type": "description",
|
383
|
+
"name": "PowerPoint Translator",
|
384
|
+
"description": "A service that provides PowerPoint translation capabilities using AWS Bedrock models",
|
385
|
+
"tools": [
|
386
|
+
{
|
387
|
+
"name": "translate_ppt",
|
388
|
+
"description": "Translate PowerPoint documents to a specified language",
|
389
|
+
"parameters": {
|
390
|
+
"type": "object",
|
391
|
+
"properties": {
|
392
|
+
"input_file": {
|
393
|
+
"type": "string",
|
394
|
+
"description": "Path to the input PowerPoint file"
|
395
|
+
},
|
396
|
+
"target_language": {
|
397
|
+
"type": "string",
|
398
|
+
"description": "Target language code (e.g., zh-CN, en, ja, ko, fr, de, es)",
|
399
|
+
"default": default_target_language
|
400
|
+
},
|
401
|
+
"output_file": {
|
402
|
+
"type": "string",
|
403
|
+
"description": "Path to save the translated PowerPoint file (if not provided, will be auto-generated)"
|
404
|
+
},
|
405
|
+
"translation_method": {
|
406
|
+
"type": "string",
|
407
|
+
"description": "Translation method to use: 'nova' (faster) or 'claude' (higher quality)",
|
408
|
+
"default": "nova",
|
409
|
+
"enum": ["nova", "claude"]
|
410
|
+
}
|
411
|
+
},
|
412
|
+
"required": ["input_file"]
|
413
|
+
}
|
414
|
+
},
|
415
|
+
{
|
416
|
+
"name": "list_supported_languages",
|
417
|
+
"description": "List all supported target languages for translation",
|
418
|
+
"parameters": {
|
419
|
+
"type": "object",
|
420
|
+
"properties": {}
|
421
|
+
}
|
422
|
+
}
|
423
|
+
]
|
424
|
+
}
|
425
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
426
|
+
sys.stdout.flush()
|
427
|
+
|
428
|
+
else:
|
429
|
+
# Unknown request type
|
430
|
+
response = {
|
431
|
+
"type": "error",
|
432
|
+
"error": f"Unknown request type: {request.get('type')}"
|
433
|
+
}
|
434
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
435
|
+
sys.stdout.flush()
|
436
|
+
|
437
|
+
except Exception as e:
|
438
|
+
logger.error(f"Error handling MCP request: {str(e)}")
|
439
|
+
response = {
|
440
|
+
"type": "error",
|
441
|
+
"error": f"Error handling request: {str(e)}"
|
442
|
+
}
|
443
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
444
|
+
sys.stdout.flush()
|
445
|
+
|
446
|
+
def main():
|
447
|
+
"""Main function"""
|
448
|
+
parser = argparse.ArgumentParser(description='PowerPoint Translator')
|
449
|
+
parser.add_argument('--mcp', action='store_true', help='Run in MCP mode')
|
450
|
+
parser.add_argument('--translate', action='store_true', help='Translate a PowerPoint file')
|
451
|
+
parser.add_argument('--input-file', help='Path to the input PowerPoint file')
|
452
|
+
parser.add_argument('--target-language', default=default_target_language, help='Target language code')
|
453
|
+
parser.add_argument('--output-file', help='Path to save the translated file')
|
454
|
+
parser.add_argument('--method', default='nova', choices=['nova', 'claude'], help='Translation method')
|
455
|
+
parser.add_argument('--list-languages', action='store_true', help='List supported languages')
|
456
|
+
|
457
|
+
args = parser.parse_args()
|
458
|
+
|
459
|
+
if args.mcp:
|
460
|
+
print("Starting PowerPoint Translator in MCP mode...")
|
461
|
+
while True:
|
462
|
+
try:
|
463
|
+
handle_mcp_request()
|
464
|
+
except KeyboardInterrupt:
|
465
|
+
break
|
466
|
+
except Exception as e:
|
467
|
+
logger.error(f"Error in MCP mode: {str(e)}")
|
468
|
+
break
|
469
|
+
|
470
|
+
elif args.translate:
|
471
|
+
if not args.input_file:
|
472
|
+
print("Error: Input file is required for translation")
|
473
|
+
parser.print_help()
|
474
|
+
sys.exit(1)
|
475
|
+
|
476
|
+
result = translate_ppt(
|
477
|
+
args.input_file,
|
478
|
+
args.target_language,
|
479
|
+
args.output_file,
|
480
|
+
args.method
|
481
|
+
)
|
482
|
+
|
483
|
+
if 'error' in result:
|
484
|
+
print(f"Error: {result['error']}")
|
485
|
+
sys.exit(1)
|
486
|
+
else:
|
487
|
+
print(f"Translation completed successfully!")
|
488
|
+
print(f"Input file: {result['input_file']}")
|
489
|
+
print(f"Output file: {result['output_file']}")
|
490
|
+
print(f"Target language: {result['target_language']}")
|
491
|
+
print(f"Translation method: {result['translation_method']}")
|
492
|
+
print(f"Translated {result['translated_texts_count']} text elements")
|
493
|
+
|
494
|
+
elif args.list_languages:
|
495
|
+
languages = list_supported_languages()
|
496
|
+
print("Supported languages:")
|
497
|
+
for code, name in languages.items():
|
498
|
+
print(f" {code}: {name}")
|
499
|
+
|
500
|
+
else:
|
501
|
+
# Default to MCP mode when no arguments are provided
|
502
|
+
print("Starting PowerPoint Translator in MCP mode...")
|
503
|
+
# Send initial description
|
504
|
+
response = {
|
505
|
+
"type": "description",
|
506
|
+
"name": "PowerPoint Translator",
|
507
|
+
"description": "A service that provides PowerPoint translation capabilities using AWS Bedrock models",
|
508
|
+
"tools": [
|
509
|
+
{
|
510
|
+
"name": "translate_ppt",
|
511
|
+
"description": "Translate PowerPoint documents to a specified language",
|
512
|
+
"parameters": {
|
513
|
+
"type": "object",
|
514
|
+
"properties": {
|
515
|
+
"input_file": {
|
516
|
+
"type": "string",
|
517
|
+
"description": "Path to the input PowerPoint file"
|
518
|
+
},
|
519
|
+
"target_language": {
|
520
|
+
"type": "string",
|
521
|
+
"description": "Target language code (e.g., zh-CN, en, ja, ko, fr, de, es)",
|
522
|
+
"default": default_target_language
|
523
|
+
},
|
524
|
+
"output_file": {
|
525
|
+
"type": "string",
|
526
|
+
"description": "Path to save the translated PowerPoint file (if not provided, will be auto-generated)"
|
527
|
+
},
|
528
|
+
"translation_method": {
|
529
|
+
"type": "string",
|
530
|
+
"description": "Translation method to use: 'nova' (faster) or 'claude' (higher quality)",
|
531
|
+
"default": "nova",
|
532
|
+
"enum": ["nova", "claude"]
|
533
|
+
}
|
534
|
+
},
|
535
|
+
"required": ["input_file"]
|
536
|
+
}
|
537
|
+
},
|
538
|
+
{
|
539
|
+
"name": "list_supported_languages",
|
540
|
+
"description": "List all supported target languages for translation",
|
541
|
+
"parameters": {
|
542
|
+
"type": "object",
|
543
|
+
"properties": {}
|
544
|
+
}
|
545
|
+
}
|
546
|
+
]
|
547
|
+
}
|
548
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
549
|
+
sys.stdout.flush()
|
550
|
+
|
551
|
+
while True:
|
552
|
+
try:
|
553
|
+
handle_mcp_request()
|
554
|
+
except KeyboardInterrupt:
|
555
|
+
break
|
556
|
+
except Exception as e:
|
557
|
+
logger.error(f"Error in MCP mode: {str(e)}")
|
558
|
+
break
|
559
|
+
|
560
|
+
if __name__ == "__main__":
|
561
|
+
try:
|
562
|
+
main()
|
563
|
+
except Exception as e:
|
564
|
+
logger.error(f"Error: {str(e)}")
|
565
|
+
sys.exit(1)
|
package/test_local.sh
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# This script tests the PowerPoint Translator MCP service locally
|
4
|
+
|
5
|
+
# Get the directory of this script
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
7
|
+
|
8
|
+
# Install dependencies
|
9
|
+
echo "Installing dependencies..."
|
10
|
+
pip install -r "$SCRIPT_DIR/requirements.txt"
|
11
|
+
|
12
|
+
# Create a test PowerPoint file if it doesn't exist
|
13
|
+
if [ ! -f "$SCRIPT_DIR/test.pptx" ]; then
|
14
|
+
echo "Creating test PowerPoint file..."
|
15
|
+
python -c "
|
16
|
+
from pptx import Presentation
|
17
|
+
from pptx.util import Inches
|
18
|
+
|
19
|
+
# Create a presentation
|
20
|
+
prs = Presentation()
|
21
|
+
|
22
|
+
# Add a slide with a title and content
|
23
|
+
slide_layout = prs.slide_layouts[1] # Title and content layout
|
24
|
+
slide = prs.slides.add_slide(slide_layout)
|
25
|
+
|
26
|
+
# Set the title
|
27
|
+
title = slide.shapes.title
|
28
|
+
title.text = 'Test PowerPoint for Translation'
|
29
|
+
|
30
|
+
# Add content
|
31
|
+
content = slide.placeholders[1]
|
32
|
+
content.text = 'This is a test PowerPoint file for translation.\n\nIt contains some text that will be translated to another language.\n\nAWS Bedrock is a powerful service for AI/ML tasks.'
|
33
|
+
|
34
|
+
# Save the presentation
|
35
|
+
prs.save('$SCRIPT_DIR/test.pptx')
|
36
|
+
print('Test PowerPoint file created at $SCRIPT_DIR/test.pptx')
|
37
|
+
"
|
38
|
+
fi
|
39
|
+
|
40
|
+
# Run the MCP server
|
41
|
+
echo "Starting PowerPoint Translator MCP server..."
|
42
|
+
node "$SCRIPT_DIR/index.js"
|