File size: 12,454 Bytes
f1ac8c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
"""
Python to JavaScript Translator using Fireworks AI (Kimi K2-0905)
Single-file Gradio app for code translation with editable output and notes.
"""

import gradio as gr
import json
import requests
from typing import Optional, Tuple, Union
from pydantic import BaseModel, ValidationError
import tempfile
import os

# Configurable constants - adjust these if needed
BASE_URL = "https://api.fireworks.ai/inference/v1"  # Fireworks OpenAI-compatible endpoint
MODEL_ID = "accounts/fireworks/models/kimi-k2-instruct-0905"  # Kimi K2 model on Fireworks
MAX_INPUT_CHARS = 20000  # Character limit for input code

class TranslationResponse(BaseModel):
    """Validated response schema from the model"""
    js_code: str
    notes: str

def create_system_prompt() -> str:
    """Create the system prompt for strict JSON output"""
    return """You are a precise code translator. Task: translate Python β†’ modern JavaScript.
Requirements:
- Output STRICT JSON with keys: "js_code" (string), "notes" (string).
- No prose outside JSON. Do not wrap in markdown. Do not include triple backticks.
- Be faithful to logic. Avoid hallucinated imports. Use idiomatic JS.
- Prefer ES modules, async/await where appropriate, and clear error handling.
- If Python uses libraries without direct JS equivalent, stub or suggest closest widely used package and explain in notes.
- If behavior is ambiguous, call it out in notes.
- Include in "notes": any assumptions, required packages, runtime constraints (Node/browser), performance/security concerns, differences in exceptions, I/O, concurrency, numeric precision, date/time, types, and mutability."""

def create_user_prompt(python_code: str) -> str:
    """Create the user prompt with the Python code to translate"""
    return f"""Translate this Python code to JavaScript.

Source Python:
{python_code}

Context for translator:
- Target runtime: Node.js LTS or browser-friendly (choose best; specify in notes).
- Preserve functionality and public API shape where possible.
- Replace Pythonic constructs (list comps, generators, context managers) with idiomatic JS.
- For file I/O, network calls, async behavior, propose suitable JS equivalents.
- For type clarity, add minimal JSDoc comments where helpful (no TypeScript).
- If the input is a script with a __main__ guard, structure JS with a main() and an IIFE or top-level await.
- Return ONLY JSON with "js_code" and "notes"."""

def call_fireworks_api(api_key: str, python_code: str, retry_on_invalid_json: bool = True) -> Union[TranslationResponse, str]:
    """
    Call Fireworks AI API for translation
    Returns either TranslationResponse or error string
    """
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "model": MODEL_ID,
        "messages": [
            {"role": "system", "content": create_system_prompt()},
            {"role": "user", "content": create_user_prompt(python_code)}
        ],
        "temperature": 0.2,
        "top_p": 0.9,
        "max_tokens": 4000
    }
    
    try:
        response = requests.post(
            f"{BASE_URL}/chat/completions",
            headers=headers,
            json=payload,
            timeout=30
        )
        
        if response.status_code != 200:
            return f"API Error: {response.status_code} - {response.text[:200]}"
        
        result = response.json()
        content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
        
        # Try to parse as JSON
        try:
            # Clean up common issues
            content = content.strip()
            if content.startswith("```json"):
                content = content[7:]
            if content.startswith("```"):
                content = content[3:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()
            
            parsed = json.loads(content)
            return TranslationResponse(**parsed)
            
        except (json.JSONDecodeError, ValidationError) as e:
            if retry_on_invalid_json:
                # Retry with stricter prompt
                payload["messages"].append({
                    "role": "assistant", 
                    "content": content
                })
                payload["messages"].append({
                    "role": "user",
                    "content": "Your response was not valid JSON. Please respond with ONLY valid JSON containing 'js_code' and 'notes' fields. No markdown, no backticks, just raw JSON."
                })
                return call_fireworks_api(api_key, python_code, retry_on_invalid_json=False)
            else:
                return f"Failed to parse model response as JSON. Raw response:\n\n{content}"
                
    except requests.exceptions.Timeout:
        return "Request timed out. Please try again."
    except requests.exceptions.RequestException as e:
        return f"Network error: {str(e)}"
    except Exception as e:
        return f"Unexpected error: {str(e)}"

def translate(
    api_key: str,
    python_text: str,
    file_upload,
    want_notes: bool
) -> Tuple[str, str, gr.update]:
    """
    Main translation function
    Returns: (js_code, notes_markdown, status_message)
    """
    # Validate API key
    if not api_key or not api_key.strip():
        return "", "❌ **Error:** Please provide your Fireworks API key", gr.update(value="Missing API key", visible=True)
    
    # Determine input source
    python_code = ""
    status_msg = ""
    
    if file_upload is not None:
        try:
            with open(file_upload.name, 'r', encoding='utf-8') as f:
                python_code = f.read()
            status_msg = "Using uploaded file"
        except Exception as e:
            return "", f"❌ **Error reading file:** {str(e)}", gr.update(value="File read error", visible=True)
    elif python_text and python_text.strip():
        python_code = python_text
        status_msg = "Using pasted code"
    else:
        return "", "❌ **Error:** Please provide Python code either by pasting or uploading a file", gr.update(value="No input provided", visible=True)
    
    # Check input size
    if len(python_code) > MAX_INPUT_CHARS:
        truncated = python_code[:MAX_INPUT_CHARS]
        warning = f"⚠️ **Warning:** Input truncated from {len(python_code)} to {MAX_INPUT_CHARS} characters\n\n"
        python_code = truncated
    else:
        warning = ""
    
    # Show character count
    char_count = f"Input: {len(python_code)} characters"
    
    # Call API
    result = call_fireworks_api(api_key.strip(), python_code)
    
    if isinstance(result, str):
        # Error occurred
        return "", f"{warning}❌ **Translation Error:**\n\n{result}", gr.update(value=f"Error: {result[:50]}...", visible=True)
    
    # Success
    js_code = result.js_code
    notes = result.notes if want_notes else ""
    
    notes_display = f"{warning}## πŸ“‹ Translation Notes\n\n{notes}" if notes else f"{warning}*Notes disabled*"
    
    return js_code, notes_display, gr.update(value=f"βœ… Translation successful | {char_count}", visible=True)

def download_js(js_code: str) -> Tuple[str, bytes]:
    """
    Create downloadable JS file
    Returns: (filename, content_bytes)
    """
    if not js_code:
        return None
    
    # Create temporary file
    with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False, encoding='utf-8') as f:
        f.write(js_code)
        temp_path = f.name
    
    return temp_path

# Example snippets
EXAMPLE_SNIPPETS = {
    "Simple Function": '''def greet(name):
    """Greet a person with their name."""
    return f"Hello, {name}!"

if __name__ == "__main__":
    print(greet("World"))''',
    
    "File I/O": '''import json

def read_config(filepath):
    """Read configuration from JSON file."""
    with open(filepath, 'r') as f:
        config = json.load(f)
    return config

def save_results(data, output_file):
    """Save results to JSON file."""
    with open(output_file, 'w') as f:
        json.dump(data, f, indent=2)
    print(f"Results saved to {output_file}")''',
    
    "Class with Inheritance": '''class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species
    
    def make_sound(self):
        return "Some generic sound"
    
    def info(self):
        return f"{self.name} is a {self.species}"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, "Canine")
        self.breed = breed
    
    def make_sound(self):
        return "Woof!"
    
    def fetch(self, item):
        return f"{self.name} fetched the {item}!"'''
}

def load_example(example_name: str) -> str:
    """Load an example snippet"""
    return EXAMPLE_SNIPPETS.get(example_name, "")

# Build Gradio interface
with gr.Blocks(title="Python β†’ JavaScript Translator", theme=gr.themes.Soft()) as app:
    gr.Markdown(
        """
        # πŸβ†’πŸ“œ Python β†’ JavaScript Translator
        ### Powered by Kimi K2-0905 on Fireworks AI
        
        Translate Python code to modern, idiomatic JavaScript with detailed migration notes.
        """
    )
    
    # Row 1: API Key
    with gr.Row():
        api_key_box = gr.Textbox(
            label="πŸ”‘ Fireworks API Key",
            placeholder="fw-...",
            type="password",
            scale=3,
            info="Your key is used only for this session"
        )
    
    # Row 2: Main content
    with gr.Row():
        # Left column: Input
        with gr.Column(scale=1):
            gr.Markdown("### πŸ“₯ Input")
            
            # Example dropdown
            example_dropdown = gr.Dropdown(
                choices=list(EXAMPLE_SNIPPETS.keys()),
                label="Load Example",
                value=None,
                interactive=True
            )
            
            python_text = gr.Code(
                label="Python Code",
                language="python",
                lines=15,
                interactive=True
            )
            
            file_upload = gr.File(
                label="Or upload a .py file",
                file_types=[".py"],
                type="filepath"
            )
            
            want_notes = gr.Checkbox(
                label="Return step-by-step notes & caveats",
                value=True
            )
            
            translate_btn = gr.Button(
                "πŸš€ Translate to JavaScript",
                variant="primary",
                size="lg"
            )
        
        # Right column: Output
        with gr.Column(scale=1):
            gr.Markdown("### πŸ“€ Output")
            
            with gr.Tabs():
                with gr.Tab("JavaScript (editable)"):
                    js_output = gr.Code(
                        label="Generated JavaScript",
                        language="javascript",
                        lines=15,
                        interactive=True
                    )
 
                
                with gr.Tab("Considerations & Notes"):
                    notes_output = gr.Markdown(
                        value="*Translation notes will appear here*"
                    )
    
    # Status message (initially hidden)
    status_msg = gr.Textbox(
        label="Status",
        visible=False,
        interactive=False
    )
    
    # Footer
    gr.Markdown(
        """
        ---
        ⚠️ **Important:** Always review and test generated code. This tool provides a starting point for migration but may require manual adjustments for complex scenarios.
        
        πŸ“ **Tips:**
        - The model excels at translating algorithmic logic and data structures
        - For library-specific code, check the notes for recommended JS equivalents
        - Generated code targets modern ES6+ JavaScript
        """
    )
    
    # Wire up interactions
    example_dropdown.change(
        fn=load_example,
        inputs=[example_dropdown],
        outputs=[python_text]
    )
    
    translate_btn.click(
        fn=translate,
        inputs=[api_key_box, python_text, file_upload, want_notes],
        outputs=[js_output, notes_output, status_msg]
    )
    

if __name__ == "__main__":
    app.launch(
        share=False,
        server_name="0.0.0.0",
        server_port=7860,
        show_error=True
    )