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-webfluxfornece 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áriothinkingBudget(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_KEYdeve 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():
- Constrói o prompt: Pega o template da operação + conteúdo
- Cria o cliente Gemini: Usa a API key fornecida
- Chama a IA: Envia o prompt para o Gemini processar
- 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}`;
}
})();- Captura o texto: Injeta script na aba ativa usando
chrome.scripting(Manifest V3). - Validação: Verifica se há texto selecionado.
- Chamada à API: POST para o servidor com feedback de “Processando…”.
- Exibição: Mostra o resumo ou mensagem de erro.
4.4 background.js
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });4.5 Carregar a extensão
- Abra Chrome →
chrome://extensions/ - Ative “Modo de desenvolvedor”
- Clique em “Carregar sem compactação” e selecione a pasta
extension/
5. Como usar
- Certifique‑se de que o servidor está rodando em
http://localhost:8080 - Selecione qualquer trecho de texto em uma página web
- Clique no ícone da extensão na barra de ferramentas (ou pressione o atalho)
- O resumo aparecerá automaticamente no painel lateral
6. Sugestões de Melhoria
- Tratamento de Erros
- Adicionar
try‑catchnofrontende@ExceptionHandlernocontrollerevita crashes e melhora UX
- Adicionar
- 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!