fix
This commit is contained in:
@@ -79,7 +79,7 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
try {
|
||||
return generateImageWithReference(prompt, referenceLogo.getBytes());
|
||||
} catch (IOException e) {
|
||||
logger.error("Ошибка при чтении MultipartFile логотипа: {}", e.getMessage());
|
||||
logger.error("Error reading MultipartFile logo: {}", e.getMessage());
|
||||
return generateImage(prompt);
|
||||
}
|
||||
}
|
||||
@@ -104,24 +104,26 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
}
|
||||
|
||||
String endpointUrl = String.format(VERTEX_API_TEMPLATE, location, projectId, location, model);
|
||||
|
||||
// Очищаем промпт от прямых указаний "написать текст"
|
||||
String finalPrompt = sanitizePromptForText(prompt);
|
||||
|
||||
Map<String, Object> requestBody = buildImagenRequestBody(finalPrompt, referenceImageBytes);
|
||||
|
||||
Map<String, Object> response = webClient.post()
|
||||
.uri(URI.create(endpointUrl))
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||
.retryWhen(createRetrySpecFor429())
|
||||
.block(Duration.ofMillis(timeoutMs));
|
||||
|
||||
lastRequestTimestamp = System.currentTimeMillis();
|
||||
return extractImageFromImagenResponse(response);
|
||||
// Первая попытка: с логотипом (если он есть)
|
||||
try {
|
||||
Map<String, Object> requestBody = buildImagenRequestBody(finalPrompt, referenceImageBytes);
|
||||
Map<String, Object> response = executeVertexRequest(endpointUrl, accessToken, requestBody);
|
||||
lastRequestTimestamp = System.currentTimeMillis();
|
||||
return extractImageFromImagenResponse(response);
|
||||
} catch (WebClientResponseException.BadRequest badRequestEx) {
|
||||
// Если API вернул 400 Bad Request из-за кривого референса, пробуем сгенерировать БЕЗ него
|
||||
if (referenceImageBytes != null && referenceImageBytes.length > 0) {
|
||||
logger.warn("Vertex AI rejected the request with reference image (400 Bad Request). Retrying WITHOUT reference image...");
|
||||
Map<String, Object> fallbackBody = buildImagenRequestBody(finalPrompt, null);
|
||||
Map<String, Object> fallbackResponse = executeVertexRequest(endpointUrl, accessToken, fallbackBody);
|
||||
lastRequestTimestamp = System.currentTimeMillis();
|
||||
return extractImageFromImagenResponse(fallbackResponse);
|
||||
} else {
|
||||
throw badRequestEx;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Fatal error in NanoBanana (Vertex AI): {}", e.getMessage(), e);
|
||||
@@ -131,6 +133,18 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> executeVertexRequest(String endpointUrl, String accessToken, Map<String, Object> requestBody) {
|
||||
return webClient.post()
|
||||
.uri(URI.create(endpointUrl))
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||
.retryWhen(createRetrySpecFor429())
|
||||
.block(Duration.ofMillis(timeoutMs));
|
||||
}
|
||||
|
||||
private String getAccessTokenFromResources() {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(CREDENTIALS_FILE_PATH);
|
||||
@@ -149,17 +163,14 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
}
|
||||
}
|
||||
|
||||
// Умная очистка промпта: не позволяем ИИ пытаться писать текст буквами
|
||||
private String sanitizePromptForText(String originalPrompt) {
|
||||
if (originalPrompt == null) return "High quality, photorealistic image";
|
||||
|
||||
// Убираем фразы типа "integrate the brand name 'XXX'"
|
||||
String sanitized = originalPrompt.replaceAll("(?i)integrate the brand name '[^']+'", "integrate the provided logo image");
|
||||
sanitized = sanitized.replaceAll("(?i)with the text '[^']+'", "with the provided logo");
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕННЫЙ МЕТОД ФОРМИРОВАНИЯ JSON
|
||||
private Map<String, Object> buildImagenRequestBody(String prompt, byte[] referenceImageBytes) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
List<Map<String, Object>> instances = new ArrayList<>();
|
||||
@@ -167,26 +178,30 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
|
||||
instance.put("prompt", prompt);
|
||||
|
||||
// Правильная структура referenceImage для Imagen 3
|
||||
if (referenceImageBytes != null && referenceImageBytes.length > 0) {
|
||||
String base64Image = Base64.getEncoder().encodeToString(referenceImageBytes);
|
||||
|
||||
Map<String, Object> referenceImageParams = new HashMap<>();
|
||||
|
||||
Map<String, Object> imageBytesMap = new HashMap<>();
|
||||
|
||||
imageBytesMap.put("bytesBase64Encoded", base64Image);
|
||||
// Желательно указывать MIME тип, чтобы Imagen не путался
|
||||
imageBytesMap.put("mimeType", "image/png");
|
||||
|
||||
referenceImageParams.put("referenceImage", imageBytesMap);
|
||||
// Для интеграции логотипа/предмета в сцену Google рекомендует использовать "SUBJECT" или не указывать тип вообще
|
||||
// В v1beta API структура referenceImages немного отличается.
|
||||
referenceImageParams.put("referenceType", "SUBJECT");
|
||||
|
||||
// STYLE заставляет ИИ использовать загруженное фото как логотип/паттерн,
|
||||
// а не пытаться впихнуть 3D-модель объекта. Для логотипов это работает лучше, чем SUBJECT.
|
||||
referenceImageParams.put("referenceType", "STYLE");
|
||||
|
||||
// Для Imagen 3 referenceImages передается на уровне instance, а не параметров
|
||||
List<Map<String, Object>> refImagesList = new ArrayList<>();
|
||||
refImagesList.add(referenceImageParams);
|
||||
|
||||
instance.put("referenceImages", refImagesList);
|
||||
|
||||
// Если есть референс, усиливаем промпт, чтобы он наклеил его, а не писал текст
|
||||
instance.put("prompt", prompt + ". Use the provided reference image as a logo or decal on the main object. Do NOT generate any custom text or typography.");
|
||||
// Если используем SUBJECT, промпт должен четко описывать сцену вокруг этого объекта
|
||||
instance.put("prompt", prompt + ". The image must feature the exact provided reference object/logo naturally integrated into the scene.");
|
||||
}
|
||||
|
||||
instances.add(instance);
|
||||
@@ -197,8 +212,7 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
parameters.put("aspectRatio", "1:1");
|
||||
parameters.put("safetyFilterLevel", "block_some");
|
||||
parameters.put("personGeneration", "allow_adult");
|
||||
|
||||
// Жесткий негативный промпт против генерации кривого текста
|
||||
// Жесткий негативный промпт
|
||||
parameters.put("negativePrompt", "text, typography, letters, words, writing, signature, watermark, nsfw, nudity, violence, deformed, ugly, blurry, distorted");
|
||||
|
||||
body.put("parameters", parameters);
|
||||
|
||||
Reference in New Issue
Block a user