359 lines
10 KiB
TypeScript
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
|
|
);
|
|
}
|