From dc80b683c28daf5e063978b53bc81f767d4e7069 Mon Sep 17 00:00:00 2001 From: kazachilo Date: Fri, 14 Mar 2025 17:38:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F:=201)=20=D1=81=D0=B5=D0=B3=D0=B4=D0=B0=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B8=D0=BC=D1=8B=D0=B9=20=D1=8D=D1=84=D1=84=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=20=D0=B7=D0=B0=D1=82=D0=B5=D0=BC=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=20=D0=BD=D0=B0=D0=B4=D0=BF=D0=B8=D1=81=D1=8C?= =?UTF-8?q?=D1=8E=20'=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=20=D1=84?= =?UTF-8?q?=D0=BE=D1=82=D0=BE'=202)=20=D0=A1=D0=B1=D1=80=D0=BE=D1=81=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D0=B8=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=203)=20=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=B1=D0=BD=D0=B0=D1=80=D1=83=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/UploadPhotoBlock.module.css | 6 +- src/screens/CropPhoto.tsx | 2 +- src/screens/Home.tsx | 25 +++- src/services/api.ts | 8 +- src/services/translateService.ts | 119 +++++++++++++++++- src/types/api.ts | 1 + 6 files changed, 145 insertions(+), 16 deletions(-) diff --git a/src/components/blocks/UploadPhotoBlock.module.css b/src/components/blocks/UploadPhotoBlock.module.css index 9b9c234..8970382 100644 --- a/src/components/blocks/UploadPhotoBlock.module.css +++ b/src/components/blocks/UploadPhotoBlock.module.css @@ -97,15 +97,11 @@ display: flex; align-items: center; justify-content: center; - opacity: 0; + opacity: 1; /* Всегда видимый */ transition: opacity 0.2s ease; border-radius: var(--border-radius); } -.hasPreview:hover .changeOverlay { - opacity: 1; -} - .changeText { color: white; font-weight: 600; diff --git a/src/screens/CropPhoto.tsx b/src/screens/CropPhoto.tsx index 6e6c2bf..4f88a97 100644 --- a/src/screens/CropPhoto.tsx +++ b/src/screens/CropPhoto.tsx @@ -264,7 +264,7 @@ const CropPhoto: React.FC = () => { // Убираем префикс data:image/jpeg;base64, оставляем только данные const imageData = previewUrl.split(',')[1]; - // Сохраняем данные в localStorage для сохранения между сеансами навигации + // Сохраняем данные в localStorage для навигации между страницами localStorage.setItem('stickerPreviewUrl', previewUrl); localStorage.setItem('stickerImageData', imageData); diff --git a/src/screens/Home.tsx b/src/screens/Home.tsx index adf7872..75270a9 100644 --- a/src/screens/Home.tsx +++ b/src/screens/Home.tsx @@ -69,8 +69,14 @@ const Home: React.FC = () => { // Проверяем, была ли ошибка перевода if (response.translationFailed) { - setNotificationTitle('Ошибка перевода'); - setNotificationMessage('Не удалось перевести промпт. Генерация отменена. Пожалуйста, попробуйте другой промпт или повторите попытку позже.'); + setNotificationTitle('Недопустимый промпт'); + setNotificationMessage('Промпт содержит недопустимый контент. Пожалуйста, используйте более нейтральные формулировки.'); + + // Логирование деталей ошибки для отладки + if (response.errorDetails) { + console.error('Детали ошибки перевода:', response.errorDetails); + } + setIsLoading(false); 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) => { diff --git a/src/services/api.ts b/src/services/api.ts index 1a7be03..4165a6a 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -159,12 +159,14 @@ async generateImage(imageData: string, style?: string, promptId?: string, userPr usedPrompt = translationResult.text; } else { // Перевод не удался после всех попыток - console.error('Не удалось перевести промпт ни через одну из моделей'); + console.error('Не удалось перевести промпт:', translationResult.text); translationFailed = true; - // Не продолжаем генерацию, возвращаем ошибку + // Не продолжаем генерацию, возвращаем ошибку с сообщением return { - translationFailed: true + translationFailed: true, + usedPrompt: 'Недопустимый промпт', // Сообщение для пользователя + errorDetails: translationResult.text // Детали ошибки для отладки }; } } diff --git a/src/services/translateService.ts b/src/services/translateService.ts index 22e9689..c699792 100644 --- a/src/services/translateService.ts +++ b/src/services/translateService.ts @@ -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 = { // Старый метод перевода через LibreTranslate (сохраняем, но не используем) async translateToEnglish(text: string, maxRetries = 3): Promise<{ success: boolean; text: string }> { @@ -106,6 +183,9 @@ const translateService = { return { success: true, text }; } + // Логирование запроса + console.log(`Запрос на перевод текста: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`); + // Последовательно пробуем каждую модель for (let i = 0; i < models.length; i++) { const model = models[i]; @@ -125,14 +205,14 @@ const translateService = { messages: [ { 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', content: text } ], - temperature: 0.3, + temperature: 0.2, // Снижаем температуру для более предсказуемых ответов max_tokens: 200 }) }); @@ -146,28 +226,57 @@ const translateService = { const data: OpenRouterResponse = await response.json(); const translatedText = data.choices[0].message.content.trim(); + // Логирование ответа + console.log(`Ответ от модели ${model}: "${translatedText.substring(0, 100)}${translatedText.length > 100 ? '...' : ''}"`); + // Проверка, что получили непустой ответ if (!translatedText) { 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()) { console.warn(`Модель ${model} вернула исходный текст без перевода`); 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); return { success: true, text: translatedText }; } catch (error) { console.error(`Ошибка перевода через модель ${model}:`, error); - // Продолжаем со следующей моделью, если она есть + + // Если это была последняя модель, прекращаем попытки + if (i === models.length - 1) { + console.error('Все попытки перевода через LLM не удались'); + return { success: false, text: 'Недопустимый промпт' }; + } + // Иначе продолжаем со следующей моделью (только при технических ошибках) } } - // Если все модели не сработали + // Этот код не должен выполниться, но оставляем для полноты console.error('Все попытки перевода через LLM не удались'); - return { success: false, text }; + return { success: false, text: 'Недопустимый промпт' }; } }; diff --git a/src/types/api.ts b/src/types/api.ts index 7036b5b..3b18a66 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -66,4 +66,5 @@ export interface GenerationResult { result?: GenerationResponse; usedPrompt?: string; translationFailed: boolean; + errorDetails?: string; // Детали ошибки для отладки }