Merge remote-tracking branch 'refs/remotes/origin/analyze-graphics' into analyze-graphics
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import io.netty.resolver.DefaultAddressResolverGroup;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
@@ -16,21 +16,18 @@ import reactor.netty.http.client.HttpClient;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
@Service
|
||||
public class NanoBananaImageGenerationService implements ImageGenerationService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NanoBananaImageGenerationService.class);
|
||||
|
||||
// Шаблон URL для Vertex AI (не Generative Language!)
|
||||
private static final String VERTEX_API_TEMPLATE = "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predict";
|
||||
private static final String CREDENTIALS_FILE_PATH = "keys/google-key.json";
|
||||
|
||||
private final Semaphore semaphore = new Semaphore(1);
|
||||
private volatile long lastRequestTimestamp = 0;
|
||||
@@ -59,9 +56,8 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
.resolver(DefaultAddressResolverGroup.INSTANCE)
|
||||
.responseTimeout(Duration.ofMillis(120000));
|
||||
|
||||
// Увеличиваем буфер памяти для приема больших картинок (Base64)
|
||||
ExchangeStrategies strategies = ExchangeStrategies.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(20 * 1024 * 1024)) // 20MB
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(20 * 1024 * 1024))
|
||||
.build();
|
||||
|
||||
this.webClient = WebClient.builder()
|
||||
@@ -85,25 +81,20 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
Thread.sleep(rateLimitDelayMs - timeSinceLast);
|
||||
}
|
||||
|
||||
String accessToken = getAccessToken();
|
||||
String accessToken = getAccessTokenFromResources();
|
||||
if (accessToken == null) {
|
||||
logger.error("Failed to get Access Token. Check 'gcloud auth login' or JSON key file.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Формируем URL для Vertex AI
|
||||
// Пример: https://us-central1-aiplatform.googleapis.com/...
|
||||
String endpointUrl = String.format(VERTEX_API_TEMPLATE, location, projectId, location, model);
|
||||
|
||||
String enrichedPrompt = enrichPromptForGemini(prompt);
|
||||
Map<String, Object> requestBody = buildImagenRequestBody(enrichedPrompt);
|
||||
|
||||
logger.info("Sending request to Vertex AI. Project: {}, Model: {}", projectId, model);
|
||||
|
||||
// 3. Выполняем POST запрос
|
||||
Map<String, Object> response = webClient.post()
|
||||
.uri(URI.create(endpointUrl))
|
||||
.header("Authorization", "Bearer " + accessToken) // Авторизация через токен
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
@@ -128,23 +119,28 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getAccessToken() {
|
||||
private String getAccessTokenFromResources() {
|
||||
try {
|
||||
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault()
|
||||
.createScoped("https://www.googleapis.com/auth/cloud-platform");
|
||||
credentials.refreshIfExpired();
|
||||
AccessToken token = credentials.getAccessToken();
|
||||
return token.getTokenValue();
|
||||
ClassPathResource resource = new ClassPathResource(CREDENTIALS_FILE_PATH);
|
||||
if (!resource.exists()) {
|
||||
logger.error("Google Credentials file not found at classpath: {}", CREDENTIALS_FILE_PATH);
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
GoogleCredentials credentials = GoogleCredentials.fromStream(is)
|
||||
.createScoped("https://www.googleapis.com/auth/cloud-platform");
|
||||
credentials.refreshIfExpired();
|
||||
return credentials.getAccessToken().getTokenValue();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Error obtaining Google Credentials", e);
|
||||
logger.error("Error loading Google Credentials from resources", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> buildImagenRequestBody(String prompt) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
|
||||
List<Map<String, Object>> instances = new ArrayList<>();
|
||||
Map<String, Object> instance = new HashMap<>();
|
||||
instance.put("prompt", prompt);
|
||||
@@ -152,8 +148,11 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
body.put("instances", instances);
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("sampleCount", 1); // Количество картинок
|
||||
parameters.put("aspectRatio", "1:1"); // Пропорции
|
||||
parameters.put("sampleCount", 1);
|
||||
parameters.put("aspectRatio", "1:1");
|
||||
parameters.put("safetyFilterLevel", "block_some");
|
||||
parameters.put("personGeneration", "allow_adult");
|
||||
parameters.put("negativePrompt", "nsfw, nudity, sexual content, lgbt symbols, rainbow flags, provocative clothing, violence, gore, blood, deformed, ugly, watermark, text, signature");
|
||||
|
||||
body.put("parameters", parameters);
|
||||
return body;
|
||||
@@ -162,26 +161,19 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
@SuppressWarnings("unchecked")
|
||||
private byte[] extractImageFromImagenResponse(Map<String, Object> response) {
|
||||
if (response == null) return null;
|
||||
|
||||
try {
|
||||
List<Map<String, Object>> predictions = (List<Map<String, Object>>) response.get("predictions");
|
||||
|
||||
if (predictions != null && !predictions.isEmpty()) {
|
||||
Map<String, Object> firstPrediction = predictions.get(0);
|
||||
|
||||
// Imagen на Vertex AI обычно отдает поле "bytesBase64Encoded"
|
||||
String base64Image = (String) firstPrediction.get("bytesBase64Encoded");
|
||||
|
||||
if (base64Image == null) {
|
||||
// Резервный вариант, если формат API изменится
|
||||
base64Image = (String) firstPrediction.get("b64");
|
||||
}
|
||||
|
||||
if (base64Image != null) {
|
||||
return java.util.Base64.getDecoder().decode(base64Image);
|
||||
}
|
||||
}
|
||||
logger.warn("No image data found in Vertex AI response. Response: {}", response);
|
||||
logger.warn("No image data found in Vertex AI response.");
|
||||
} catch (Exception e) {
|
||||
logger.error("Parsing error: {}", e.getMessage());
|
||||
}
|
||||
@@ -196,6 +188,7 @@ public class NanoBananaImageGenerationService implements ImageGenerationService
|
||||
}
|
||||
|
||||
private String enrichPromptForGemini(String original) {
|
||||
return "high quality, photorealistic image of: " + original;
|
||||
return "Professional, high quality, photorealistic image of: " + original +
|
||||
". Style: neutral, business-appropriate, culturally respectful.";
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ openai.image.style=natural
|
||||
# Set to 'nano-banana' to use Google Gemini Nano Banana for image generation
|
||||
image.generation.provider=nano-banana
|
||||
|
||||
google.cloud.project-id=onyx-yeti-456518-d4
|
||||
google.cloud.project-id=data-totality-483616-t5
|
||||
google.cloud.location=us-central1
|
||||
|
||||
google.gemini.image.model=imagen-3.0-generate-001
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "data-totality-483616-t5",
|
||||
"private_key_id": "e9d6df08cfaf391f2b45ccf0473d22221a14fe1e",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkA41lMjU7GSLj\nS5CeXJjT3gsKTp2/Y9adieLrjIXA8GY3Dhm+dwj1+hVF1fKB1qYI072LNku9jpuA\nGbKtDAIDXfTr0NKF097z51+X7Sprnq3lamqzAXnH8PEAdv5A97GtNBDBB6E9S40q\n6uEBfIjeaVENcrt8bnS/jgvtQeDFj/0RoFVOUfapCgFW2zkvqU549z0kZumF9zNF\nNXkKGOelmWm1430QyCWhw4P5SIxfCehz0pD17NSMlUZQvbav/ZZ286SnDzloRT+q\njXyRIXnMMuTnKWov7zqKflmfg8omxkwf/HqGIu2c0+hgmcyQhy8oXj/X9ocXLNsA\n+IclAskFAgMBAAECggEAD+ZHl5GhnGbdksg5WZ4OCK05R8NYQnyktPqayg0szYfg\neMzVTQ6lX+cMQeFcKXRhUxtnqq5Vnad3Rpih0Wmya5jOTJVJRe+Z6O4Qk8ifeKBW\nKOvDMogy7IwxvhhK9zRWf+e7X2MPIUMziP9iVu5iJvbHBIqPR0rpPBqX6GT0VmJs\n6CG1C1FtL3QFvxiZSiWwIzGb0ozZKIHBhqUxapQcUlTSqsSNguJEGeHUXkD3Eg1R\n3gQJs4JKCiQnsCeWcpEcoo/GaP+feTjXDJzPlx6EDzpaFAeyUXPWNm8s8VBHj/dM\nvD9FSL2/YiouublF7TW+cvCCfiYALLJx41hOeeEZTwKBgQDhtbdY1FQwh6T8ciF2\nCH+kWfIUIIiNUwlyllc05Vx6nC9oSuChDOWFMqe2TOpcibAvPCWgLN4lHjxsd0zj\nInTomGFg8mPu7l9g0AUCt6R2DoWWnS2omtyRTCIiq0Ms8WHUwil3U57IS7+2MgK7\nlKwBQtrBQoWZPkgwiuxypLf/uwKBgQC6BkP3B2u2LbP5v0VlNYI1Lvr96Bk6sWCh\ny1kOO0DI/6ubC5WFgTDtTBakbZvRdk5X7OmJCaMRtNCgrAK1twq8dLT5EZFNRrQ/\n+Q/mGsMBnZlf15hqUVnjmYuXy+o9hnU1F84TOjz0AYwvZFmoWicYWf6NDoHfnXyw\nMQOlQMHuPwKBgQC4wBFPuJ0hDCbULGePQe2ZDbbFGswrQejdzombVpKYGfgEGiY8\nLV5B5PC6m8LJ9Dour8Dsl01OfK1g61YYJUAkk3ZB8KY0ERIpIJy5WLBZohFe81FI\nTVp15Cec0SqvQvWdPUDL17qEoNxPuifaexs//QUqY9UPVpeohE5+mK2zuQKBgD8S\nGMEMlwxxem/W1eR2u+rkPt0PcF5h/W1d0Jfupwx/AuPSjaNyJfCOt66QKyBdiDpv\nbM61ocBDGekf6NtLcCxtNV6tAiK9MifufeF6eVt236WLJ3YElwF/8cBJtuWdTOfv\nZkNGn5vrWmhHEc7uogqzmIaiwI3ci4YBWEcTKa1pAoGAQAGOKD6/9m15rGoypQK0\ndM7RHoFiPAga0l6RaRmjInhkPItood84nWvSGwmfDMovvLuMXT4/kqhgoAJBCTp4\nZMWlEnk5XgU3XVATV9fWogSEdPiXq+mwikl2A7xF/PsS79ekc6wVAFS6aog08GH+\ndwLlquOtv2Dd9qW6d6Pe+4I=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "vertex-ai-user@data-totality-483616-t5.iam.gserviceaccount.com",
|
||||
"client_id": "116751741947593459625",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vertex-ai-user%40data-totality-483616-t5.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
Reference in New Issue
Block a user