лучшения: 1) сегда видимый эффект затемнения с надписью 'зменить фото' 2) Сброс изображения при закрытии приложения 3) лучшенное обнаружение отказов модели при переводе

This commit is contained in:
kazachilo 2025-03-14 17:38:23 +03:00
parent b18acdbad5
commit dc80b683c2
6 changed files with 145 additions and 16 deletions

View File

@ -97,15 +97,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
opacity: 0; opacity: 1; /* Всегда видимый */
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
.hasPreview:hover .changeOverlay {
opacity: 1;
}
.changeText { .changeText {
color: white; color: white;
font-weight: 600; font-weight: 600;

View File

@ -264,7 +264,7 @@ const CropPhoto: React.FC = () => {
// Убираем префикс data:image/jpeg;base64, оставляем только данные // Убираем префикс data:image/jpeg;base64, оставляем только данные
const imageData = previewUrl.split(',')[1]; const imageData = previewUrl.split(',')[1];
// Сохраняем данные в localStorage для сохранения между сеансами навигации // Сохраняем данные в localStorage для навигации между страницами
localStorage.setItem('stickerPreviewUrl', previewUrl); localStorage.setItem('stickerPreviewUrl', previewUrl);
localStorage.setItem('stickerImageData', imageData); localStorage.setItem('stickerImageData', imageData);

View File

@ -69,8 +69,14 @@ const Home: React.FC = () => {
// Проверяем, была ли ошибка перевода // Проверяем, была ли ошибка перевода
if (response.translationFailed) { if (response.translationFailed) {
setNotificationTitle('Ошибка перевода'); setNotificationTitle('Недопустимый промпт');
setNotificationMessage('Не удалось перевести промпт. Генерация отменена. Пожалуйста, попробуйте другой промпт или повторите попытку позже.'); setNotificationMessage('Промпт содержит недопустимый контент. Пожалуйста, используйте более нейтральные формулировки.');
// Логирование деталей ошибки для отладки
if (response.errorDetails) {
console.error('Детали ошибки перевода:', response.errorDetails);
}
setIsLoading(false); setIsLoading(false);
return; return;
} }
@ -167,6 +173,21 @@ const Home: React.FC = () => {
); );
} }
}, []); }, []);
// Эффект для обработки закрытия приложения
useEffect(() => {
// Обработчик события beforeunload для очистки данных при закрытии приложения
const handleBeforeUnload = () => {
localStorage.removeItem('stickerPreviewUrl');
localStorage.removeItem('stickerImageData');
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, []);
// Функция для получения кнопок в зависимости от блока // Функция для получения кнопок в зависимости от блока
const getBlockButtons = useCallback((block: any) => { const getBlockButtons = useCallback((block: any) => {

View File

@ -159,12 +159,14 @@ async generateImage(imageData: string, style?: string, promptId?: string, userPr
usedPrompt = translationResult.text; usedPrompt = translationResult.text;
} else { } else {
// Перевод не удался после всех попыток // Перевод не удался после всех попыток
console.error('Не удалось перевести промпт ни через одну из моделей'); console.error('Не удалось перевести промпт:', translationResult.text);
translationFailed = true; translationFailed = true;
// Не продолжаем генерацию, возвращаем ошибку // Не продолжаем генерацию, возвращаем ошибку с сообщением
return { return {
translationFailed: true translationFailed: true,
usedPrompt: 'Недопустимый промпт', // Сообщение для пользователя
errorDetails: translationResult.text // Детали ошибки для отладки
}; };
} }
} }

View File

@ -41,6 +41,83 @@ interface OpenRouterResponse {
}; };
} }
/**
* Проверяет, содержит ли текст признаки отказа от модели
* @param text Текст для проверки
* @returns true, если текст похож на отказ модели, иначе false
*/
function isModelRefusal(text: string): boolean {
if (!text) return false;
// Проверка на специальный маркер отказа
if (text.trim() === 'TRANSLATION_FAILED') {
console.log('Обнаружен специальный маркер отказа: TRANSLATION_FAILED');
return true;
}
const lowerText = text.toLowerCase();
// Типичные фразы отказа от моделей
const refusalPhrases = [
// Специальные маркеры
"translation_failed",
"translation failed",
"cannot translate",
// Стандартные фразы отказа
"i'm sorry",
"i apologize",
"i cannot",
"i'm not able to",
"i am unable to",
"i can't",
"cannot assist",
"unable to provide",
"against my ethical guidelines",
"violates content policy",
"not going to translate",
"contains derogatory",
"racial slurs",
"offensive content",
"inappropriate language",
"inappropriate content",
"harmful content",
"offensive language",
"offensive terms",
"violates",
"policy",
"guidelines"
];
for (const phrase of refusalPhrases) {
if (lowerText.includes(phrase)) {
console.log(`Обнаружена фраза отказа модели: ${phrase}`);
return true;
}
}
// Проверка на наличие кавычек и ключевых слов
const keywordsWithQuotes = [
{ keyword: "translate", quoteRequired: true },
{ keyword: "translation", quoteRequired: true },
{ keyword: "content", quoteRequired: false },
{ keyword: "inappropriate", quoteRequired: false },
{ keyword: "offensive", quoteRequired: false }
];
for (const item of keywordsWithQuotes) {
const hasKeyword = lowerText.includes(item.keyword);
const hasQuotes = lowerText.includes('"') || lowerText.includes("'");
if (hasKeyword && (!item.quoteRequired || hasQuotes)) {
console.log(`Обнаружено ключевое слово "${item.keyword}" ${item.quoteRequired ? "с кавычками" : "без требования кавычек"}`);
return true;
}
}
return false;
}
const translateService = { const translateService = {
// Старый метод перевода через LibreTranslate (сохраняем, но не используем) // Старый метод перевода через LibreTranslate (сохраняем, но не используем)
async translateToEnglish(text: string, maxRetries = 3): Promise<{ success: boolean; text: string }> { async translateToEnglish(text: string, maxRetries = 3): Promise<{ success: boolean; text: string }> {
@ -106,6 +183,9 @@ const translateService = {
return { success: true, text }; return { success: true, text };
} }
// Логирование запроса
console.log(`Запрос на перевод текста: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
// Последовательно пробуем каждую модель // Последовательно пробуем каждую модель
for (let i = 0; i < models.length; i++) { for (let i = 0; i < models.length; i++) {
const model = models[i]; const model = models[i];
@ -125,14 +205,14 @@ const translateService = {
messages: [ messages: [
{ {
role: 'system', role: 'system',
content: 'You are a professional translator. Translate the following text from any language to English. Preserve the meaning and style. Only return the translated text without any additional comments or explanations.' content: 'You are a professional translator. Your task is to translate the following text from any language to English. ONLY return the translated text without ANY additional comments, explanations, or quotation marks. Do not include phrases like "Translation:" or "Translated text:". If you cannot translate the text for any reason (such as it containing offensive, inappropriate, or harmful content), respond with "TRANSLATION_FAILED" and nothing else.'
}, },
{ {
role: 'user', role: 'user',
content: text content: text
} }
], ],
temperature: 0.3, temperature: 0.2, // Снижаем температуру для более предсказуемых ответов
max_tokens: 200 max_tokens: 200
}) })
}); });
@ -146,28 +226,57 @@ const translateService = {
const data: OpenRouterResponse = await response.json(); const data: OpenRouterResponse = await response.json();
const translatedText = data.choices[0].message.content.trim(); const translatedText = data.choices[0].message.content.trim();
// Логирование ответа
console.log(`Ответ от модели ${model}: "${translatedText.substring(0, 100)}${translatedText.length > 100 ? '...' : ''}"`);
// Проверка, что получили непустой ответ // Проверка, что получили непустой ответ
if (!translatedText) { if (!translatedText) {
throw new Error('Empty translation result'); throw new Error('Empty translation result');
} }
// Проверка на отказ модели
if (isModelRefusal(translatedText)) {
console.warn(`Модель ${model} отказалась переводить текст. Полный ответ: "${translatedText}"`);
// Если это основная модель, сразу возвращаем ошибку
// и не пытаемся использовать резервную модель
if (model === TRANSLATION_MODELS.PRIMARY) {
return { success: false, text: 'Недопустимый промпт' };
} else {
throw new Error('Model refused to translate');
}
}
// Проверка, что перевод отличается от исходного текста // Проверка, что перевод отличается от исходного текста
if (translatedText.toLowerCase().trim() === text.toLowerCase().trim()) { if (translatedText.toLowerCase().trim() === text.toLowerCase().trim()) {
console.warn(`Модель ${model} вернула исходный текст без перевода`); console.warn(`Модель ${model} вернула исходный текст без перевода`);
throw new Error('Translation returned original text'); throw new Error('Translation returned original text');
} }
// Проверка на подозрительные паттерны в переводе
const punctuationRatio = (translatedText.match(/[.,!?;:()[\]{}'"]/g) || []).length / translatedText.length;
if (punctuationRatio > 0.3) { // Если более 30% текста - знаки препинания
console.warn(`Подозрительно высокое содержание знаков препинания в переводе: ${(punctuationRatio * 100).toFixed(1)}%`);
throw new Error('Suspicious translation result');
}
console.log(`Успешный перевод через модель ${model}:`, translatedText); console.log(`Успешный перевод через модель ${model}:`, translatedText);
return { success: true, text: translatedText }; return { success: true, text: translatedText };
} catch (error) { } catch (error) {
console.error(`Ошибка перевода через модель ${model}:`, error); console.error(`Ошибка перевода через модель ${model}:`, error);
// Продолжаем со следующей моделью, если она есть
// Если это была последняя модель, прекращаем попытки
if (i === models.length - 1) {
console.error('Все попытки перевода через LLM не удались');
return { success: false, text: 'Недопустимый промпт' };
}
// Иначе продолжаем со следующей моделью (только при технических ошибках)
} }
} }
// Если все модели не сработали // Этот код не должен выполниться, но оставляем для полноты
console.error('Все попытки перевода через LLM не удались'); console.error('Все попытки перевода через LLM не удались');
return { success: false, text }; return { success: false, text: 'Недопустимый промпт' };
} }
}; };

View File

@ -66,4 +66,5 @@ export interface GenerationResult {
result?: GenerationResponse; result?: GenerationResponse;
usedPrompt?: string; usedPrompt?: string;
translationFailed: boolean; translationFailed: boolean;
errorDetails?: string; // Детали ошибки для отладки
} }