This commit is contained in:
arys
2026-03-01 14:11:02 +05:00
parent 679fe31c69
commit ddeba641ba
@@ -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);