Implement minimal translation system prompt following Russell's approach
This commit is contained in:
parent
12662d67c9
commit
bd09659d3b
2 changed files with 122 additions and 16 deletions
|
|
@ -1,5 +1,7 @@
|
|||
// Translation functionality with code block preservation
|
||||
import { sendMessage } from './chat.js';
|
||||
import { API_KEY } from './config.js';
|
||||
import { getSelectedModel, getSelectedModelEndpoint } from './models.js';
|
||||
|
||||
// Languages supported by Hermes 3 model (from Russell's implementation)
|
||||
export const SUPPORTED_LANGUAGES = {
|
||||
|
|
@ -24,12 +26,87 @@ export const SUPPORTED_LANGUAGES = {
|
|||
'ko': 'Korean'
|
||||
};
|
||||
|
||||
// Send message with custom history (for translation with minimal system prompt)
|
||||
async function* sendMessageWithHistory(messageHistory) {
|
||||
const apiUrl = `${getSelectedModelEndpoint()}/chat/completions`;
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: getSelectedModel(),
|
||||
messages: messageHistory,
|
||||
temperature: 0.3, // Lower temperature for more consistent translation
|
||||
max_tokens: 8192,
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith('data: ')) {
|
||||
const jsonData = line.slice(6);
|
||||
if (jsonData === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(jsonData);
|
||||
const content = parsedData.choices[0].delta.content;
|
||||
if (content) {
|
||||
yield content;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer = lines[lines.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve code blocks, URLs, and special formatting during translation
|
||||
export function preserveSpecialContent(text) {
|
||||
const preservations = [];
|
||||
let preservedText = text;
|
||||
|
||||
// Code blocks - triple backticks (most important to preserve completely)
|
||||
// HTML code elements (most important in HTML context)
|
||||
preservedText = preservedText.replace(/<pre[\s\S]*?<\/pre>/gi, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__CODE_${index}__`;
|
||||
preservations.push({ type: 'CODE', content: match, placeholder });
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
preservedText = preservedText.replace(/<code[\s\S]*?<\/code>/gi, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__CODE_${index}__`;
|
||||
preservations.push({ type: 'CODE', content: match, placeholder });
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Other code-related HTML elements
|
||||
preservedText = preservedText.replace(/<(kbd|samp|var)[\s\S]*?<\/\1>/gi, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__CODE_${index}__`;
|
||||
preservations.push({ type: 'CODE', content: match, placeholder });
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Markdown code blocks (for mixed content)
|
||||
preservedText = preservedText.replace(/```[\s\S]*?```/g, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__CODE_${index}__`;
|
||||
|
|
@ -37,7 +114,7 @@ export function preserveSpecialContent(text) {
|
|||
return placeholder;
|
||||
});
|
||||
|
||||
// Inline code
|
||||
// Inline markdown code
|
||||
preservedText = preservedText.replace(/`[^`\n]+`/g, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__CODE_${index}__`;
|
||||
|
|
@ -61,7 +138,7 @@ export function preserveSpecialContent(text) {
|
|||
return placeholder;
|
||||
});
|
||||
|
||||
// HTML tags
|
||||
// Other HTML tags (after code elements are preserved)
|
||||
preservedText = preservedText.replace(/<[^>]+>/g, (match) => {
|
||||
const index = preservations.length;
|
||||
const placeholder = `__HTML_${index}__`;
|
||||
|
|
@ -76,39 +153,67 @@ export function preserveSpecialContent(text) {
|
|||
export function restoreSpecialContent(translatedText, preservations) {
|
||||
let restoredText = translatedText;
|
||||
|
||||
preservations.forEach((item) => {
|
||||
// Use the exact placeholder that was created for this item
|
||||
restoredText = restoredText.replace(new RegExp(item.placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), item.content);
|
||||
});
|
||||
// Simple replacement like Russell's approach
|
||||
for (const item of preservations) {
|
||||
restoredText = restoredText.replace(new RegExp(item.placeholder, 'g'), item.content);
|
||||
}
|
||||
|
||||
return restoredText;
|
||||
}
|
||||
|
||||
// Translate text using Hermes AI
|
||||
// Translate text using Hermes AI with minimal system context
|
||||
export async function translateText(text, targetLanguage) {
|
||||
const { preservedText, preservations } = preserveSpecialContent(text);
|
||||
|
||||
const prompt = `Translate the following text to ${SUPPORTED_LANGUAGES[targetLanguage]}.
|
||||
// Debug logging
|
||||
console.log('=== TRANSLATION DEBUG ===');
|
||||
console.log('Original text:', text.substring(0, 300) + (text.length > 300 ? '...' : ''));
|
||||
console.log('Preserved text:', preservedText.substring(0, 300) + (preservedText.length > 300 ? '...' : ''));
|
||||
console.log('Preservations found:', preservations.length);
|
||||
preservations.forEach((item, index) => {
|
||||
console.log(` ${index}: ${item.placeholder} -> ${item.content.substring(0, 50)}${item.content.length > 50 ? '...' : ''}`);
|
||||
});
|
||||
|
||||
const prompt = `Translate the following HTML content into ${SUPPORTED_LANGUAGES[targetLanguage]}. The input is in English and formatted as HTML, which includes formatting, code blocks, and technical content. Ensure the output preserves the structure, syntax, and formatting of the original. Do not translate or modify placeholders like __CODE_0__, __URI_0__, __HTML_0__, etc., as they represent code blocks, URLs, or HTML elements that should remain unchanged. Only translate the surrounding text. Here is the text to translate:
|
||||
|
||||
CRITICAL: Do NOT translate or modify ANY placeholders that look like __CODE_X__, __URI_X__, or __HTML_X__ (where X is a number). These represent code blocks, URLs, and HTML tags that must remain EXACTLY as written.
|
||||
${preservedText}`;
|
||||
|
||||
Text to translate:
|
||||
${preservedText}
|
||||
|
||||
Translate only the regular text, keeping all placeholders unchanged. Provide only the translation without explanations.`;
|
||||
// Use minimal system message for translation - focused purely on the task
|
||||
const translationHistory = [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a translation assistant. Translate text accurately while preserving all formatting and placeholders exactly as provided."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt
|
||||
}
|
||||
];
|
||||
|
||||
let translatedText = '';
|
||||
|
||||
try {
|
||||
for await (const chunk of sendMessage(prompt)) {
|
||||
for await (const chunk of sendMessageWithHistory(translationHistory)) {
|
||||
translatedText += chunk;
|
||||
}
|
||||
|
||||
// Clean up the response (remove any extra explanations)
|
||||
translatedText = translatedText.trim();
|
||||
console.log('AI response length:', translatedText.length);
|
||||
console.log('AI response:', translatedText.substring(0, 300) + (translatedText.length > 300 ? '...' : ''));
|
||||
|
||||
// Check if placeholders are still in the response
|
||||
preservations.forEach((item, index) => {
|
||||
const found = translatedText.includes(item.placeholder);
|
||||
console.log(` Placeholder ${item.placeholder} found in response: ${found}`);
|
||||
});
|
||||
|
||||
// Restore preserved content
|
||||
return restoreSpecialContent(translatedText, preservations);
|
||||
const restored = restoreSpecialContent(translatedText, preservations);
|
||||
console.log('Restored text length:', restored.length);
|
||||
console.log('Restored text:', restored.substring(0, 300) + (restored.length > 300 ? '...' : ''));
|
||||
|
||||
return restored;
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error);
|
||||
throw new Error('Translation failed. Please try again.');
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ window.sendMessage = Chat.sendMessage;
|
|||
window.speakText = TTS.speakText;
|
||||
window.uploadFile = FileUpload.uploadFile;
|
||||
window.openTTSModal = UI.openTTSModal;
|
||||
window.openTranslateModal = UI.openTranslateModal;
|
||||
window.toggleHermesModal = async () => await UI.toggleHermesModal();
|
||||
window.extractWebpageContent = Content.extractWebpageContent;
|
||||
window.getSelectedModel = Models.getSelectedModel;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue