aethex-studio/src/lib/translation-engine.ts

359 lines
10 KiB
TypeScript

/**
* Cross-Platform Code Translation Engine
* Core competitive differentiator for AeThex Studio
*/
import { PlatformId, getPlatform } from './platforms';
import { toast } from 'sonner';
import { trackEvent, trackError } from './analytics';
export interface TranslationRequest {
sourceCode: string;
sourcePlatform: PlatformId;
targetPlatform: PlatformId;
context?: string;
}
export interface TranslationResult {
success: boolean;
translatedCode?: string;
explanation?: string;
warnings?: string[];
error?: string;
}
/**
* Platform-specific translation prompts
*/
const getTranslationPrompt = (
sourceCode: string,
sourcePlatform: PlatformId,
targetPlatform: PlatformId,
context?: string
): string => {
const sourcePlat = getPlatform(sourcePlatform);
const targetPlat = getPlatform(targetPlatform);
return `You are an expert game developer specializing in cross-platform game development.
**Task**: Translate the following ${sourcePlat.language} code (${sourcePlat.displayName}) to ${targetPlat.language} (${targetPlat.displayName}).
**Source Platform**: ${sourcePlat.displayName}
- Language: ${sourcePlat.language}
- API Documentation: ${sourcePlat.apiDocs}
**Target Platform**: ${targetPlat.displayName}
- Language: ${targetPlat.language}
- API Documentation: ${targetPlat.apiDocs}
**Source Code**:
\`\`\`${sourcePlat.language.toLowerCase()}
${sourceCode}
\`\`\`
${context ? `**Additional Context**: ${context}\n` : ''}
**Instructions**:
1. Translate the code to ${targetPlat.language} while preserving the logic and functionality
2. Use ${targetPlat.displayName}-native APIs and best practices
3. Add comments explaining platform-specific differences
4. Ensure the code follows ${targetPlat.language} conventions and style
5. If certain features don't have direct equivalents, provide the closest alternative and explain
**Output Format**:
Return ONLY the translated code wrapped in triple backticks with the language identifier.
Then provide a brief explanation of key changes and any warnings.
Example:
\`\`\`${targetPlat.fileExtension.replace('.', '')}
// Translated code here
\`\`\`
**Explanation**: [Brief explanation of translation]
**Warnings**: [Any caveats or limitations, if applicable]`;
};
/**
* Platform-specific translation rules
*/
const platformTranslationRules: Record<string, string[]> = {
'roblox-to-uefn': [
'game:GetService() → Use Verse imports',
'Instance.new() → object{} syntax in Verse',
'Connect() → Subscribe() in Verse',
'wait() → Sleep() in Verse',
'print() → Print() in Verse',
],
'uefn-to-roblox': [
'Verse imports → game:GetService()',
'object{} → Instance.new()',
'Subscribe() → Connect()',
'Sleep() → wait()',
'Print() → print()',
],
'roblox-to-spatial': [
'Lua → TypeScript syntax',
'game:GetService() → Spatial SDK imports',
'Instance.new() → new SpatialObject()',
'Connect() → addEventListener()',
'wait() → await setTimeout()',
],
'spatial-to-roblox': [
'TypeScript → Lua syntax',
'Spatial SDK → game:GetService()',
'new SpatialObject() → Instance.new()',
'addEventListener() → Connect()',
'await setTimeout() → wait()',
],
};
/**
* Mock translation service (for development without API key)
* Replace with actual Claude API call in production
*/
async function translateWithMockService(
request: TranslationRequest
): Promise<TranslationResult> {
const ruleKey = `${request.sourcePlatform}-to-${request.targetPlatform}`;
const rules = platformTranslationRules[ruleKey] || [];
return {
success: true,
translatedCode: `-- Translated from ${request.sourcePlatform} to ${request.targetPlatform}
-- Translation Rules Applied:
${rules.map(r => `-- ${r}`).join('\n')}
-- Original Code (needs actual translation):
${request.sourceCode}
-- TODO: Replace with actual ${request.targetPlatform} implementation`,
explanation: `This is a mock translation. The actual translation engine will use Claude API to intelligently convert ${request.sourcePlatform} code to ${request.targetPlatform}.`,
warnings: [
'Mock translation active - integrate Claude API for production',
`Translation rules: ${rules.join(', ')}`,
],
};
}
/**
* Translate code using Claude API
* This is the production implementation
*/
async function translateWithClaudeAPI(
request: TranslationRequest
): Promise<TranslationResult> {
try {
// Check if API key is configured
const apiKey = process.env.NEXT_PUBLIC_CLAUDE_API_KEY;
if (!apiKey) {
console.warn('Claude API key not configured, using mock translation');
return await translateWithMockService(request);
}
const prompt = getTranslationPrompt(
request.sourceCode,
request.sourcePlatform,
request.targetPlatform,
request.context
);
// Call Claude API
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: [
{
role: 'user',
content: prompt,
},
],
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
`API request failed: ${response.statusText} (${response.status})${
errorData.error?.message ? ` - ${errorData.error.message}` : ''
}`
);
}
const data = await response.json();
const content = data.content?.[0]?.text;
if (!content) {
throw new Error('No content in API response');
}
// Parse the response to extract code, explanation, and warnings
const result = parseClaudeResponse(content, request.targetPlatform);
return result;
} catch (error) {
trackError(error as Error, {
context: 'translation_api',
sourcePlatform: request.sourcePlatform,
targetPlatform: request.targetPlatform,
});
// Fallback to mock if API fails
console.warn('Claude API failed, falling back to mock:', error);
return await translateWithMockService(request);
}
}
/**
* Parse Claude API response to extract code, explanation, and warnings
*/
function parseClaudeResponse(
content: string,
targetPlatform: PlatformId
): TranslationResult {
try {
// Extract code block (supports multiple formats)
const codeMatch = content.match(/```(?:verse|lua|typescript|ts|javascript|js)?\n([\s\S]*?)```/);
if (!codeMatch) {
// If no code block found, treat entire response as code
return {
success: true,
translatedCode: content.trim(),
explanation: 'Translation completed',
};
}
const translatedCode = codeMatch[1].trim();
// Extract explanation (multiple formats)
const explanationMatch =
content.match(/\*\*Explanation\*\*:\s*(.*?)(?:\n\*\*|$)/s) ||
content.match(/Explanation:\s*(.*?)(?:\n\*\*|$)/s) ||
content.match(/## Explanation\s*(.*?)(?:\n##|$)/s);
// Extract warnings (multiple formats)
const warningsMatch =
content.match(/\*\*Warnings\*\*:\s*([\s\S]*?)(?:\n\*\*|$)/) ||
content.match(/Warnings:\s*([\s\S]*?)(?:\n\*\*|$)/) ||
content.match(/## Warnings\s*([\s\S]*?)(?:\n##|$)/);
const warnings = warningsMatch
? warningsMatch[1]
.split('\n')
.map(w => w.trim().replace(/^[-•*]\s*/, ''))
.filter(w => w.length > 0)
: undefined;
return {
success: true,
translatedCode,
explanation: explanationMatch ? explanationMatch[1].trim() : undefined,
warnings: warnings && warnings.length > 0 ? warnings : undefined,
};
} catch (error) {
console.error('Error parsing Claude response:', error);
return {
success: true,
translatedCode: content.trim(),
explanation: 'Translation completed (parsing error)',
warnings: ['Response parsing encountered issues, code may need review'],
};
}
}
/**
* Main translation function
*/
export async function translateCode(
request: TranslationRequest
): Promise<TranslationResult> {
try {
// Validate platforms
if (request.sourcePlatform === request.targetPlatform) {
return {
success: false,
error: 'Source and target platforms must be different',
};
}
if (!request.sourceCode || request.sourceCode.trim() === '') {
return {
success: false,
error: 'Source code cannot be empty',
};
}
// Log translation attempt
trackEvent('translation_started', {
sourcePlatform: request.sourcePlatform,
targetPlatform: request.targetPlatform,
codeLength: request.sourceCode.length,
});
// Perform translation
const result = await translateWithClaudeAPI(request);
// Log result
if (result.success) {
trackEvent('translation_success', {
sourcePlatform: request.sourcePlatform,
targetPlatform: request.targetPlatform,
});
toast.success(
`Translated ${request.sourcePlatform}${request.targetPlatform}`
);
} else {
trackEvent('translation_failed', {
sourcePlatform: request.sourcePlatform,
targetPlatform: request.targetPlatform,
error: result.error,
});
toast.error(`Translation failed: ${result.error}`);
}
return result;
} catch (error) {
trackError(error as Error, { context: 'translate_code' });
return {
success: false,
error: `Unexpected error: ${(error as Error).message}`,
};
}
}
/**
* Get supported translation pairs
*/
export function getSupportedTranslations(): Array<{
source: PlatformId;
target: PlatformId;
}> {
return [
{ source: 'roblox', target: 'uefn' },
{ source: 'uefn', target: 'roblox' },
{ source: 'roblox', target: 'spatial' },
{ source: 'spatial', target: 'roblox' },
{ source: 'uefn', target: 'spatial' },
{ source: 'spatial', target: 'uefn' },
];
}
/**
* Check if translation is supported
*/
export function isTranslationSupported(
source: PlatformId,
target: PlatformId
): boolean {
return getSupportedTranslations().some(
(pair) => pair.source === source && pair.target === target
);
}