Criando uma extensão para o Chrome que resume conteúdo com o Gemini e Java Spring

Imagem de capa: Criando uma extensão para o Chrome que resume conteúdo com o Gemini e Java Spring

Este tutorial mostra como criar uma extensão Chrome que captura texto > selecionado e o resume usando o Gemini via um backend Spring.

1. Estrutura do projeto

Pré-requisitos

  • Java 21+
  • Maven 3.6+
  • Google Chrome
  • Chave de API do Gemini (obter aqui)

3. Backend – Spring

3.1 pom.xml

  • spring-boot-starter-webflux fornece um servidor reativo, ideal para chamadas externas como a do Gemini.
  • google-genai é o SDK oficial do Google para interagir com o Gemini.

3.2 GeminiConfig.java

@Configuration
public class GeminiConfig {

    @Bean
    GenerateContentConfig generateContentConfig() {
        // Disable thiking for faster response
        return GenerateContentConfig.builder()
                .thinkingConfig(ThinkingConfig.builder().thinkingBudget(0).build())
                .build();
    }
}

O que faz: Configura como o modelo Gemini vai processar as requisições.

  • @Configuration: Diz ao Spring que essa classe contém configurações
  • @Bean: Cria um objeto que o Spring vai gerenciar e injetar onde necessário
  • thinkingBudget(0): Desativa o "modo pensamento" do Gemini para respostas mais rápidas (menos processamento = menos tempo)

3.3 application.properties

gemini.api.key=${GEMINI_KEY}
gemini.api.model=${GEMINI_MODEL:gemini-2.5-flash}
  • GEMINI_KEY deve ser exportada no shell (export GEMINI_KEY=...).
  • O modelo padrão pode ser sobrescrito pela variável GEMINI_MODEL.

3.4 ResearchController.java

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*")
public class ResearchController {

    private final ResearchService service;

    public ResearchController(ResearchService service) {
        this.service = service;
    }

    @PostMapping("/process")
    public ResponseEntity<String> process(@RequestBody ResearchRequest req) {
        String summary = service.summarize(req.getContent());
        return ResponseEntity.ok(summary);
    }
}

O que faz:
Endpoint REST que recebe o texto da extensão e retorna o resumo.
@CrossOrigin(origins = "*") permite requisições do Chrome (apenas para desenvolvimento).

3.5 ResearchService.java

@Service
public class ResearchService {
    
    public String processContent(ResearchRequest researchRequest) {
        String prompt = buildPrompt(researchRequest);
        
        Client client = Client.builder()
                .apiKey(researchRequest.getApiKey())
                .build();
                
        GenerateContentResponse response = client.models.generateContent(
                geminiModel, prompt, config
        );
        
        return response.text();
    }
    
    private String buildPrompt(ResearchRequest researchRequest) {
        ResearchOperation operation = ResearchOperation.fromString(
                researchRequest.operation()
        );
        return operation.getPromptTemplate() + "\n\n" + researchRequest.content();
    }
}

O que faz: Contém a lógica principal da aplicação.

Método processContent():

  1. Constrói o prompt: Pega o template da operação + conteúdo
  2. Cria o cliente Gemini: Usa a API key fornecida
  3. Chama a IA: Envia o prompt para o Gemini processar
  4. Retorna o resultado: Texto gerado pela IA

Método buildPrompt():

  • Identifica qual operação usar (SUMMARIZE ou SUGGEST)
  • Combina o template de prompt com o conteúdo do usuário

3.6 ResearchRequest.java

public record ResearchRequest(
        String content,
        String operation,
        String apiKey) {
}

4. Extensão Chrome

4.1 manifest.json

{
  "manifest_version": 3,
  "name": "BrieflyAI",
  "version": "1.0",
  "description": "AI-powered assistant for your learning.",
  "permissions": [
    "activeTab",
    "storage",
    "sidePanel",
    "scripting"
  ],
  "action": {
    "default_title": "BrieflyAI"
  },
  "side_panel": {
    "default_path": "src/sidepanel.html"
  },
  "background": {
    "service_worker": "src/background.js"
  },
  "host_permissions": [
    "http://localhost:8080/*",
    "<all_urls>"
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  }
}
  • activeTab: Acessa a aba ativa quando o usuário clica na extensão.
  • storage: Salva anotações localmente.
  • scripting: Permite injetar código JavaScript para capturar texto selecionado.
  • host_permissions: Autoriza requisições para o servidor local.

4.2 sidepanel.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>BrieflyAI</title>
  <style>
    body { font-family: Arial, sans-serif; padding: 10px; }
    #summary { white-space: pre-wrap; margin-top: 10px; }
  </style>
</head>
<body>
  <h3>Resumo</h3>
  <textarea id="summary" rows="10" readonly></textarea>
  <script src="sidepanel.js"></script>
</body>
</html>

4.3 sidepanel.js

(async () => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  const [selection] = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => window.getSelection().toString()
  });

  const summaryEl = document.getElementById('summary');

  if (!selection.result) {
    summaryEl.value = 'Nenhum texto selecionado.';
    return;
  }

  try {
    summaryEl.value = 'Processando...';
    const res = await fetch('http://localhost:8080/api/process', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content: selection.result })
    });

    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const summary = await res.text();
    summaryEl.value = summary;
  } catch (e) {
    summaryEl.value = `Erro: ${e.message}`;
  }
})();
  1. Captura o texto: Injeta script na aba ativa usando chrome.scripting (Manifest V3).
  2. Validação: Verifica se há texto selecionado.
  3. Chamada à API: POST para o servidor com feedback de “Processando…”.
  4. Exibição: Mostra o resumo ou mensagem de erro.

4.4 background.js

chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });

4.5 Carregar a extensão

  1. Abra Chrome → chrome://extensions/
  2. Ative “Modo de desenvolvedor”
  3. Clique em “Carregar sem compactação” e selecione a pasta extension/

5. Como usar

  1. Certifique‑se de que o servidor está rodando em http://localhost:8080
  2. Selecione qualquer trecho de texto em uma página web
  3. Clique no ícone da extensão na barra de ferramentas (ou pressione o atalho)
  4. O resumo aparecerá automaticamente no painel lateral

6. Sugestões de Melhoria

  • Tratamento de Erros
    • Adicionar try‑catch no frontend e @ExceptionHandler no controller evita crashes e melhora UX
  • Validação
    • Validar tamanho máximo do texto (ex: 10.000 caracteres) para prevenir abuso da API e custos excessivos
  • UI/UX
    • Adicionar loading spinner, botão de reprocessar e feedback visual durante processamento
  • Segurança
    • Implementar rate limiting
  • Cache
    • Cachear resumos por hash do conteúdo para textos repetidos
  • Configuração
    • Permitir usuário escolher modelo Gemini na UI
  • Persistência
    • Salvar histórico para usuário poder revisar resumos anteriores

7. Referências

Gostou deste artigo? Compartilhe!