@meistrari/minuta-nuxt

Embed do editor de minuta (docx paginado A4) do defesa.ai para apps Nuxt — componente + proxy Nitro, sem segredos no browser
1,348
Install
npm install @meistrari/minuta-nuxt
Latest Version:1.2.1
Vue:3
License:Proprietary
Last Updated:Oct 15, 2026
Links: npm
Maintainer: prxg22

@meistrari/minuta-nuxt

Embed do editor de minuta do defesa.ai para qualquer app Nuxt: o documento paginado em A4 com edição rica e export DOCX fiel ao preview, renderizado como componente — sem iframe e sem segredos no browser.

Segue a mesma arquitetura do @meistrari/chat-nuxt: o componente fala com rotas Nitro registradas no próprio app hospedeiro (/api/minuta/*), e o proxy encaminha para a API do Case Defense Builder com o JWT Tela do usuário logado — a API valida o token contra a auth central e resolve o workspace.

O pacote mora em packages/minuta-nuxt do repositório case-defense-builder e é publicado no npm pelo workflow Publish @meistrari/minuta-nuxt. O próprio cliente do defesa.ai consome esta mesma versão publicada — a tela /minutas/:id é só um invólucro do <MinutaEmbed>, então bug corrigido aqui chega nos dois.

Instalação

npm install @meistrari/minuta-nuxt
// nuxt.config.ts
export default defineNuxtConfig({
    modules: ['@meistrari/minuta-nuxt'],
    minutaNuxt: {
        // URL da API do Case Defense Builder
        cdbApiUrl: process.env.CDB_API_URL, // backend do defesa.ai
        // opcional: cookie do host que carrega o JWT Tela do usuário
        // (default: event.context.auth.token → cookie `tela-jwt`)
        // authTokenCookie: 'meu-cookie-jwt',
    },
})

Uso

<template>
    <MinutaEmbed
        minuta-id="94a53f33-…"
        style="height: 100vh"
        @saved="onSaved"
    />
</template>

Criando uma minuta a partir do host (composable auto-importado):

const { uploadMinutaFile, createMinuta } = useMinutaApi()

// arquivos de QUALQUER tipo — sobem pro Vault via proxy
const files = await Promise.all([...pickedFiles].map(f => uploadMinutaFile(f)))

const minuta = await createMinuta({
    name: 'Contestação — João da Silva',
    files,
    // contexto livre sobre a peça (tipo, juízo, orientações do escritório…)
    context: 'Contestação para o réu; linha de defesa: culpa exclusiva do consumidor.',
    // agente Tela que escreve a minuta ("organizationName/repository")
    agentId: 'tela/defesa-minuta-avulsa',
})
// navegue para a tela que renderiza <MinutaEmbed :minuta-id="minuta.id" />

Para peça de texto fixo com campos variáveis, sem agente, veja Templates e variáveis.

Templates e variáveis

Nem toda peça precisa de agente. Quando o texto já está decidido e o que muda são alguns campos — petições intermediárias, ofícios, requerimentos de rotina — existe o caminho template: um documento com placeholders, escrito no mesmo editor A4, que vira minuta pronta no mesmo request, sem passar por agente nenhum.

O CDB não guarda template. Quem guarda é o app hospedeiro, que já tem cadastro para isso. O que o embed entrega é o editor e o formato; o que o CDB faz é trocar placeholder por valor.

Escrever o template

<MinutaTemplateEditor
    v-model="cadastro.templateHtml"
    :variables="CATALOGO"
    style="height: 100vh"
    @update:used-variables="keys => cadastro.variaveis = keys"
/>

O catálogo é um array plano, definido pelo host — o CDB não sabe o que significa nenhuma dessas chaves:

const CATALOGO = [
    { key: 'PROCESSO', label: 'Nº do Processo', description: 'De info_processos.processo', group: 'Processo' },
    { key: 'NOME_REU', label: 'Nome do Réu', group: 'Réu' },
]

No documento, o advogado digita / (ou usa o botão { } da barra), busca pelo rótulo e insere. O placeholder é um nó atômico: seleciona inteiro, apaga inteiro, e negrito/fonte aplicam nele por completo. Não dá para digitar uma chave que não existe, e não dá para partir uma pela metade ao formatar — que é o motivo de ser nó, e não #NOME_VARIAVEL no texto.

update:used-variables devolve as chaves usadas, na ordem do documento e sem repetição. É com ela que o host sabe o que perguntar; ele nunca precisa ler o HTML.

Gerar a peça

const minuta = await createMinuta({
    name: 'Requerimento — contrato 123',
    templateHtml: cadastro.templateHtml,
    variables: { PROCESSO: '1234567-89.2026.8.26.0100', NOME_REU: null },
})

minuta.status          // 'READY' — sem agente, sem polling
minuta.missingVariables // ['NOME_REU']

Só texto nos valores: número e data chegam já formatados pelo host, que é quem sabe se 1234.5 é R$ 1.234,50. null significa "não resolveu".

Variável sem valor não some. Ela continua sendo um placeholder vivo dentro do documento, no parágrafo em que está — quem abrir a minuta vê o buraco onde ele fica, em vez de descobrir que falta algo por um botão desabilitado. O host decide o que fazer com missingVariables (abrir pendência, bloquear o protocolo, ou deixar preencher no editor mesmo).

templateHtml e agentId se excluem: mandar os dois é 400.

Props do <MinutaTemplateEditor>

Prop Default Descrição
modelValue '' HTML do template (v-model)
variables [] catálogo { key, label, description?, group? }
editable true permite editar
showToolbar true barra com o resumo das variáveis usadas
margins 2,5cm/3cm margens da página em px @96dpi
chrome null cabeçalho/rodapé; v-model:chrome para persistir

Eventos: update:modelValue, update:usedVariables, update:chrome, ready(editor). Slots: toolbar-start, toolbar-end. defineExpose: getHtml(), usedVariables, editor.

Playground: bun run dev e abra /?template.

Props do <MinutaEmbed>

Prop Default Descrição
minutaId — id da minuta avulsa no CDB
editable true permite editar o documento
autoSave true salva 2s após a última edição
showToolbar true barra com nome, estado de save e export
margins 2,5cm/3cm margens da página em px @96dpi
renamable segue editable nome editável na própria barra
chatAgentId null id de plataforma do agente assistente; sem ele, o chat não aparece

Eventos: ready(editor), saved(minuta), error(message). Slot: toolbar-start (o host injeta navegação própria, ex.: botão voltar). defineExpose: saveNow(), exportDocx(), editor.

Dois agentes, dois lugares. O que escreve a minuta vai no agentId da chamada de criação acima — é decisão de quem cria, e cada app manda o seu (o mesmo campo existe na Data API, para quem cria do servidor; a env MINUTA_AVULSA_AGENT do CDB é só o default de quem não mandar nada). O que edita pelo chat é prop do componente (chatAgentId), porque vive dentro da tela de edição.

A barra do embed inclui Histórico: versões são criadas automaticamente conforme o documento é editado (snapshot no auto-save, com espaçamento mínimo de 10 min) e podem ser restauradas — o estado atual vira um snapshot antes.

Como funciona

  1. MinutaEmbed busca a minuta em /api/minuta/:id (rota Nitro do módulo).
  2. O proxy resolve o JWT do usuário (contexto de auth do host ou cookie) e encaminha para {cdbApiUrl}/standalone-minuta/:id com Authorization: Bearer.
  3. Enquanto status === 'PROCESSING', o embed faz polling (3s) — o próprio GET no CDB consulta o agente e materializa o resultado.
  4. Com status === 'READY', o HTML entra no editor paginado (Tiptap + motor de paginação A4 próprio). O export DOCX é 100% client-side; o Word reflui o texto como o preview (só a quebra manual do template é forçada).

Desenvolvimento

npm install
npm run build      # nuxt-module-build

O núcleo do editor (MinutaDocxEditor.vue, paginationPlugin, docxExport, editorExtensions) é portado do Case Defense Builder — a intenção é que o CDB passe a consumir este package para existir um único editor mantido.

Related Packages

@meistrari/tela-build

Look at the Nuxt 3 documentation to learn more.

87,526
@meistrari/auth-nuxt

A Nuxt module for authentication, organization management, and API key capabilit...

64,317
@meistrari/chat-nuxt

Nuxt module that adds a complete AI chat interface to your app. Server-side chat...

45,463

Version History

Version Released Vue License
1.2.1 ^3.0 Proprietary
1.2.0 ^3.0 Proprietary
1.1.1 ^3.0 Proprietary
1.1.0 ^3.0 Proprietary
1.0.0 ^3.0 Proprietary
Pre-releases (1)
Version Released Vue License
1.2.0-rc.0 ^3.0 Proprietary