diff --git a/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java b/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java index 7dd0054..e25f924 100644 --- a/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java +++ b/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java @@ -4,26 +4,26 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.util.Base64; import android.util.Log; -import com.google.ai.client.generativeai.GenerativeModel; -import com.google.ai.client.generativeai.java.GenerativeModelFutures; -import com.google.ai.client.generativeai.type.Content; -import com.google.ai.client.generativeai.type.GenerateContentResponse; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -import org.json.JSONException; +import org.json.JSONArray; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class InvoiceScannerHelper { - private static final String API_KEY = "AIzaSyCoUZSXfEk43LfPtkCCjsnQ_ZMWX7NG1xQ"; // Substitua pela sua chave API + private static final String API_URL = "https://apichat.epvc.pt/api/chat"; + private static final String MODEL_NAME = "qwen3.6"; private static final String PROMPT = "Analisa esta fatura. Devolve APENAS um objeto JSON com os campos: 'valor' (Double, apenas números e ponto decimal), 'descricao' (String curta, nome do estabelecimento ou produto), 'categoria' (String, ex: Alimentação, Transporte, Lazer, Saúde, Casa, Educação, Outros), e 'data' (String formato DD/MM/AAAA). Se não conseguires ler algum, coloca valor padrão ou string vazia."; public interface ScanCallback { @@ -32,81 +32,119 @@ public class InvoiceScannerHelper { } public static void scanInvoice(Context context, Uri imageUri, ScanCallback callback) { - if (API_KEY == null || API_KEY.isEmpty() || API_KEY.contains("CHAVE_API_KEY")) { - callback.onError("Chave API do Gemini não configurada."); - return; - } - Executor executor = Executors.newSingleThreadExecutor(); - try (InputStream imageStream = context.getContentResolver().openInputStream(imageUri)) { - Bitmap bitmap = BitmapFactory.decodeStream(imageStream); + executor.execute(() -> { + try (InputStream imageStream = context.getContentResolver().openInputStream(imageUri)) { + Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream); - if (bitmap == null) { - callback.onError("Não foi possível carregar a imagem da fatura."); - return; - } - - GenerativeModel gm = new GenerativeModel( - "gemini-1.5-flash", - API_KEY - ); - GenerativeModelFutures model = GenerativeModelFutures.from(gm); - - Content content = new Content.Builder() - .addText(PROMPT) - .addImage(bitmap) - .build(); - - ListenableFuture response = model.generateContent(content); - - Futures.addCallback(response, new FutureCallback() { - @Override - public void onSuccess(GenerateContentResponse result) { - try { - String textResponse = result.getText(); - if (textResponse != null) { - int start = textResponse.indexOf("{"); - int end = textResponse.lastIndexOf("}"); - if (start != -1 && end != -1 && end > start) { - String jsonContent = textResponse.substring(start, end + 1); - JSONObject json = new JSONObject(jsonContent); - double valor = json.optDouble("valor", 0.0); - String descricao = json.optString("descricao", ""); - String categoria = json.optString("categoria", "Outros"); - String data = json.optString("data", ""); - callback.onSuccess(valor, descricao, categoria, data); - } else { - callback.onError("Não foi possível extrair os dados formatados em JSON."); - } - } else { - callback.onError("Resposta vazia da IA."); - } - } catch (JSONException e) { - Log.e("InvoiceScanner", "Erro ao fazer parse do JSON: " + result.getText(), e); - callback.onError("Erro ao ler dados da fatura."); - } finally { - if (executor instanceof java.util.concurrent.ExecutorService) { - ((java.util.concurrent.ExecutorService) executor).shutdown(); - } - } + if (originalBitmap == null) { + callback.onError("Não foi possível carregar a imagem da fatura."); + return; } - @Override - public void onFailure(Throwable t) { - Log.e("InvoiceScanner", "Falha na API do Gemini", t); - callback.onError("Falha ao comunicar com a IA: " + t.getMessage()); - if (executor instanceof java.util.concurrent.ExecutorService) { - ((java.util.concurrent.ExecutorService) executor).shutdown(); - } - } - }, executor); + // Redimensionar a imagem para otimizar o envio + Bitmap bitmap = scaleBitmap(originalBitmap, 1024); + String base64Image = bitmapToBase64(bitmap); - } catch (Exception e) { - Log.e("InvoiceScanner", "Erro geral", e); - callback.onError("Erro ao processar imagem: " + e.getMessage()); - if (executor instanceof java.util.concurrent.ExecutorService) { - ((java.util.concurrent.ExecutorService) executor).shutdown(); + // Criar o payload JSON para o Ollama + JSONObject payload = new JSONObject(); + payload.put("model", MODEL_NAME); + payload.put("stream", false); + payload.put("format", "json"); + + JSONArray messages = new JSONArray(); + JSONObject message = new JSONObject(); + message.put("role", "user"); + message.put("content", PROMPT); + + JSONArray images = new JSONArray(); + images.put(base64Image); + message.put("images", images); + + messages.put(message); + payload.put("messages", messages); + + // Enviar a requisição HTTP + URL url = new URL(API_URL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + conn.setRequestProperty("Accept", "application/json"); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setConnectTimeout(60000); // 60 segundos de timeout + conn.setReadTimeout(60000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = conn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + br.close(); + + JSONObject responseJson = new JSONObject(response.toString()); + JSONObject messageObj = responseJson.getJSONObject("message"); + String contentStr = messageObj.getString("content"); + + // Tratar a extração robusta do JSON + int start = contentStr.indexOf("{"); + int end = contentStr.lastIndexOf("}"); + if (start != -1 && end != -1 && end > start) { + String jsonContent = contentStr.substring(start, end + 1); + JSONObject invoiceJson = new JSONObject(jsonContent); + double valor = invoiceJson.optDouble("valor", 0.0); + String descricao = invoiceJson.optString("descricao", ""); + String categoria = invoiceJson.optString("categoria", "Outros"); + String data = invoiceJson.optString("data", ""); + callback.onSuccess(valor, descricao, categoria, data); + } else { + callback.onError("Não foi possível extrair os dados formatados da fatura."); + } + } else { + callback.onError("Erro ao comunicar com a IA: Servidor respondeu com código " + responseCode); + } + + } catch (Exception e) { + Log.e("InvoiceScanner", "Erro geral", e); + callback.onError("Erro ao processar fatura: " + e.getMessage()); + } finally { + if (executor instanceof java.util.concurrent.ExecutorService) { + ((java.util.concurrent.ExecutorService) executor).shutdown(); + } } + }); + } + + private static Bitmap scaleBitmap(Bitmap bitmap, int maxDimension) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + if (width <= maxDimension && height <= maxDimension) { + return bitmap; } + float ratio = (float) width / (float) height; + int newWidth, newHeight; + if (width > height) { + newWidth = maxDimension; + newHeight = Math.round(maxDimension / ratio); + } else { + newHeight = maxDimension; + newWidth = Math.round(maxDimension * ratio); + } + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } + + private static String bitmapToBase64(Bitmap bitmap) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream); + byte[] byteArray = outputStream.toByteArray(); + return Base64.encodeToString(byteArray, Base64.NO_WRAP); } }