Demo: Buscador de Sinônimos com IA Local (Prompt API)
inicianteDigite uma palavra e receba sinônimos em PT-BR direto no navegador. Demonstra system prompt para restringir output e chips selecionáveis com a Prompt API.
Visão Geral
Demo que recebe uma palavra em português e devolve sinônimos contextualmente relevantes, apresentados como chips clicáveis. Tudo roda no navegador via Prompt API — sem servidor, sem dicionário externo.
O truque está no system prompt: instruímos o modelo a responder exclusivamente com sinônimos separados por vírgula, sem explicações. Isso mantém o output limpo e previsível sem precisar de JSON Schema.
Pra quem: Quem está começando com a Prompt API e quer ver como um system prompt bem escrito resolve 80% dos problemas de formatação de output.
Técnica principal: initialPrompts com role "system" para restringir o modelo a um formato específico (lista de palavras separadas por vírgula). Parsing simples com .split(",").
Wireframe
┌─────────────────────────────────────────────────────┐
│ 📝 Buscador de Sinônimos │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ ┌──────────┐ │
│ │ input (placeholder: "feliz") │ │ Buscar │ │
│ └─────────────────────────────────┘ └──────────┘ │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ │ │
│ │ [alegre] [contente] [satisfeito] [radiante] │ │
│ │ [jubiloso] [animado] │ │
│ │ │ │
│ │ ← chips clicáveis (copiam ao clicar) │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 💡 Clique em um sinônimo para copiar │
│ ⚠️ Requer Chrome 138+ com Prompt API habilitada │
└─────────────────────────────────────────────────────┘
HTML
<section class="demo-container" id="buscador-sinonimos">
<h2>📝 Buscador de Sinônimos</h2>
<div id="status-bar" class="status" hidden>
<span id="status-message"></span>
</div>
<form id="form-sinonimos" class="search-form">
<input
type="text"
id="input-word"
placeholder="Digite uma palavra... ex: feliz"
maxlength="50"
autocomplete="off"
required
/>
<button type="submit" id="btn-search" disabled>Buscar</button>
</form>
<div id="results" class="results-area" hidden>
<p id="results-label" class="results-label"></p>
<div id="chips-container" class="chips"></div>
</div>
<p id="copy-feedback" class="copy-feedback" hidden>✓ Copiado!</p>
</section>
Código JavaScript
class SynonymFinder {
constructor() {
this.session = null;
this.form = document.getElementById("form-sinonimos");
this.inputEl = document.getElementById("input-word");
this.btnEl = document.getElementById("btn-search");
this.resultsEl = document.getElementById("results");
this.resultsLabel = document.getElementById("results-label");
this.chipsContainer = document.getElementById("chips-container");
this.copyFeedback = document.getElementById("copy-feedback");
this.statusBar = document.getElementById("status-bar");
this.statusMessage = document.getElementById("status-message");
this.init();
}
async init() {
if (!("LanguageModel" in window)) {
this.showStatus("❌ Prompt API não disponível. Use Chrome 138+.", "error");
return;
}
const availability = await LanguageModel.availability();
if (availability === "unavailable") {
this.showStatus("❌ Modelo não disponível neste dispositivo.", "error");
return;
}
if (availability === "downloading") {
this.showStatus("⏳ Baixando modelo... Aguarde.", "loading");
}
try {
this.session = await LanguageModel.create({
initialPrompts: [{
role: "system",
content: `Você é um dicionário de sinônimos em português brasileiro.
Quando o usuário enviar uma palavra, responda APENAS com sinônimos dessa palavra separados por vírgula.
Regras:
- Retorne entre 4 e 8 sinônimos
- Apenas palavras em português brasileiro
- Sem explicações, sem numeração, sem frases
- Se a palavra não existir ou não tiver sinônimos, responda exatamente: SEM_SINONIMOS`
}],
monitor(m) {
m.addEventListener("downloadprogress", (e) => {
const pct = Math.round(e.loaded * 100);
document.getElementById("status-message").textContent =
`⏳ Baixando modelo... ${pct}%`;
});
}
});
this.btnEl.disabled = false;
this.hideStatus();
} catch (err) {
this.showStatus(`❌ Erro ao criar sessão: ${err.message}`, "error");
return;
}
this.form.addEventListener("submit", (e) => {
e.preventDefault();
this.search();
});
}
async search() {
const word = this.inputEl.value.trim().toLowerCase();
if (!word) return;
this.btnEl.disabled = true;
this.btnEl.textContent = "Buscando...";
this.resultsEl.hidden = true;
try {
const raw = await this.session.prompt(word);
const cleaned = raw.trim();
if (cleaned === "SEM_SINONIMOS") {
this.showStatus(`Não encontrei sinônimos para "${word}".`, "error");
return;
}
const synonyms = cleaned
.split(",")
.map(s => s.trim().toLowerCase())
.filter(s => s.length > 0 && s !== word);
if (synonyms.length === 0) {
this.showStatus(`Não encontrei sinônimos para "${word}".`, "error");
return;
}
this.hideStatus();
this.renderChips(word, synonyms);
} catch (err) {
if (err.name === "AbortError") return;
this.showStatus(`❌ Erro: ${err.message}`, "error");
} finally {
this.btnEl.disabled = false;
this.btnEl.textContent = "Buscar";
}
}
renderChips(word, synonyms) {
this.resultsLabel.textContent = `Sinônimos de "${word}":`;
this.chipsContainer.innerHTML = "";
synonyms.forEach(synonym => {
const chip = document.createElement("button");
chip.className = "chip";
chip.textContent = synonym;
chip.type = "button";
chip.addEventListener("click", () => this.copyToClipboard(synonym));
this.chipsContainer.appendChild(chip);
});
this.resultsEl.hidden = false;
}
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.copyFeedback.hidden = false;
setTimeout(() => { this.copyFeedback.hidden = true; }, 1500);
} catch {
// Fallback silencioso
}
}
showStatus(msg, type) {
this.statusBar.hidden = false;
this.statusBar.className = `status status-${type}`;
this.statusMessage.textContent = msg;
}
hideStatus() {
this.statusBar.hidden = true;
}
}
document.addEventListener("DOMContentLoaded", () => new SynonymFinder());
Fluxo UX
- Página carrega → verifica se
LanguageModelexiste no window - Sessão criada → botão “Buscar” habilitado; se modelo em download, mostra progresso
- Usuário digita palavra → submete com Enter ou clique no botão
- Busca executa → botão desabilita, texto muda pra “Buscando…”
- Resposta chega → chips aparecem com animação sutil (fade in)
- Clique no chip → copia palavra pro clipboard, toast ”✓ Copiado!” por 1.5s
- Nova busca → substitui chips anteriores
Edge Cases e Tratamento de Erros
| Cenário | Tratamento |
|---|---|
| Prompt API não existe | Mensagem + orientação sobre versão do Chrome |
| Modelo indisponível | Informa incompatibilidade de hardware |
| Campo vazio | required no HTML impede submit |
| Palavra inexistente | Modelo retorna SEM_SINONIMOS → mensagem amigável |
| Modelo retorna frase em vez de lista | .split(",") gera 1 chip com a frase inteira — não quebra a UI |
| Modelo repete a palavra original | Filtrada no .filter(s => s !== word) |
| Clipboard API bloqueada (HTTP) | Try/catch silencioso, chip funciona normalmente |
| Sessão expira / context overflow | Improvável (input curto), mas erro genérico capturado |
CSS Essencial
.search-form {
display: flex;
gap: 0.5rem;
}
.search-form input {
flex: 1;
padding: 0.75rem 1rem;
font-size: 1rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
transition: border-color 0.2s;
}
.search-form input:focus {
outline: none;
border-color: #3b82f6;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
}
.chip {
padding: 0.5rem 1rem;
background: #eff6ff;
color: #1d4ed8;
border: 1px solid #bfdbfe;
border-radius: 9999px;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
}
.chip:hover {
background: #dbeafe;
transform: translateY(-1px);
}
.chip:active {
transform: translateY(0);
}
.results-label {
font-size: 0.85rem;
color: #6b7280;
margin-bottom: 0.25rem;
}
.copy-feedback {
font-size: 0.8rem;
color: #16a34a;
margin-top: 0.5rem;
}
.status-error { color: #dc2626; }
.status-loading { color: #2563eb; }