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