Files
lexmind/src/app/api/documents/generate/route.ts

359 lines
14 KiB
TypeScript

import { NextRequest } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
const SYSTEM_PROMPT = `Você é um advogado sênior brasileiro com mais de 30 anos de experiência em todas as áreas do direito. Você domina completamente a legislação brasileira, incluindo mas não limitado a:
- Constituição Federal de 1988 (CF/88)
- Código de Processo Civil (CPC - Lei nº 13.105/2015)
- Código Civil (CC - Lei nº 10.406/2002)
- Código Penal (CP - Decreto-Lei nº 2.848/1940)
- Código de Processo Penal (CPP - Decreto-Lei nº 3.689/1941)
- Consolidação das Leis do Trabalho (CLT)
- Código de Defesa do Consumidor (CDC - Lei nº 8.078/1990)
- Código Tributário Nacional (CTN - Lei nº 5.172/1966)
- Estatuto da Criança e do Adolescente (ECA)
- Lei de Execução Penal (LEP)
- Lei Maria da Penha (Lei nº 11.340/2006)
- Lei de Improbidade Administrativa (Lei nº 8.429/1992)
INSTRUÇÕES RIGOROSAS:
1. ESTRUTURA: Toda peça deve seguir a estrutura formal brasileira:
- Endereçamento ao juízo competente (em caixa alta)
- Qualificação completa das partes
- DOS FATOS (narrativa clara e cronológica)
- DO DIREITO / DOS FUNDAMENTOS JURÍDICOS (argumentação robusta)
- DOS PEDIDOS (numerados e específicos)
- Requerimentos finais
- Valor da causa (quando aplicável)
- Local, data e assinatura
2. CITAÇÕES LEGAIS: Cite artigos específicos de leis reais. Use jurisprudência quando relevante (STF, STJ, TJs). Formate citações em itálico.
3. LINGUAGEM: Use português jurídico formal. Trate o juiz como "Excelentíssimo(a) Senhor(a) Doutor(a) Juiz(a)" ou "Meritíssimo". Use "data venia", "ad argumentandum tantum", e demais expressões latinas quando apropriado.
4. QUALIDADE: A peça deve ser completa, profissional e pronta para protocolar. Não deixe campos em branco - use [COMPLETAR] apenas para dados específicos do cliente que não foram fornecidos.
5. FORMATAÇÃO: Use parágrafos numerados, seções em negrito/caixa alta, e espaçamento adequado para leitura jurídica.
6. Nunca mencione que é uma IA. Escreva como um advogado real escreveria.`
function buildUserPrompt(type: string, area: string, details: Record<string, string>): string {
const typeNames: Record<string, string> = {
'peticao-inicial': 'Petição Inicial',
'contestacao': 'Contestação',
'apelacao-civel': 'Apelação Cível',
'apelacao-criminal': 'Apelação Criminal',
'recurso': 'Recurso',
'contrato': 'Contrato',
'parecer': 'Parecer Jurídico',
'impugnacao': 'Impugnação',
'habeas-corpus': 'Habeas Corpus',
'mandado-seguranca': 'Mandado de Segurança',
'embargo': 'Embargo',
'recurso-especial': 'Recurso Especial',
'agravo': 'Agravo',
'outros': 'Peça Jurídica',
}
const areaNames: Record<string, string> = {
'civil': 'Direito Civil',
'trabalhista': 'Direito Trabalhista',
'penal': 'Direito Penal',
'tributario': 'Direito Tributário',
'familia': 'Direito de Família',
'empresarial': 'Direito Empresarial',
'consumidor': 'Direito do Consumidor',
'administrativo': 'Direito Administrativo',
}
const typeName = typeNames[type] || type
const areaName = areaNames[area] || area
let prompt = `Elabore uma ${typeName} completa na área de ${areaName}.\n\n`
if (details.autor) prompt += `AUTOR/REQUERENTE: ${details.autor}\n`
if (details.reu) prompt += `RÉU/REQUERIDO: ${details.reu}\n`
if (details.fatos) prompt += `\nFATOS:\n${details.fatos}\n`
if (details.fundamentos) prompt += `\nFUNDAMENTOS JURÍDICOS RELEVANTES:\n${details.fundamentos}\n`
if (details.pedidos) prompt += `\nPEDIDOS PRETENDIDOS:\n${details.pedidos}\n`
if (details.contexto) prompt += `\nCONTEXTO ADICIONAL:\n${details.contexto}\n`
prompt += `\nElabore a peça completa, profissional e pronta para protocolar.`
return prompt
}
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return new Response(JSON.stringify({ error: 'Não autorizado' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
// Check credits
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { credits: true },
})
if (!user || user.credits < 1) {
return new Response(JSON.stringify({ error: 'Créditos insuficientes' }), {
status: 402,
headers: { 'Content-Type': 'application/json' },
})
}
const body = await req.json()
const { type, area, details } = body
if (!type || !area || !details) {
return new Response(JSON.stringify({ error: 'Dados incompletos' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const userPrompt = buildUserPrompt(type, area, details)
// Create document record
const document = await prisma.document.create({
data: {
userId: session.user.id,
type: type.toUpperCase().replace(/-/g, '_'),
title: `${type} - ${area}`,
prompt: userPrompt,
content: '',
area: area.toUpperCase(),
status: 'GENERATING',
},
})
// Deduct credit
await prisma.user.update({
where: { id: session.user.id },
data: { credits: { decrement: 1 } },
})
const OPENAI_API_KEY = process.env.OPENAI_API_KEY
if (!OPENAI_API_KEY) {
// Fallback: generate a mock response for development
const encoder = new TextEncoder()
const mockContent = generateMockDocument(type, area, details)
const stream = new ReadableStream({
async start(controller) {
const words = mockContent.split(' ')
for (let i = 0; i < words.length; i++) {
const chunk = (i === 0 ? '' : ' ') + words[i]
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content: chunk, documentId: document.id })}\n\n`))
await new Promise(r => setTimeout(r, 20))
}
// Update document
await prisma.document.update({
where: { id: document.id },
data: {
content: mockContent,
wordCount: mockContent.split(/\s+/).length,
status: 'COMPLETED',
tokens: mockContent.length,
},
})
// Log usage
await prisma.usageLog.create({
data: {
userId: session.user.id,
type: 'DOCUMENT',
tokens: mockContent.length,
cost: 0,
},
})
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true, documentId: document.id })}\n\n`))
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}
// Real OpenAI call with streaming
const openaiRes = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userPrompt },
],
stream: true,
temperature: 0.4,
max_tokens: 8000,
}),
})
if (!openaiRes.ok) {
await prisma.document.update({
where: { id: document.id },
data: { status: 'ERROR' },
})
// Refund credit
await prisma.user.update({
where: { id: session.user.id },
data: { credits: { increment: 1 } },
})
return new Response(JSON.stringify({ error: 'Erro ao gerar documento' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
const encoder = new TextEncoder()
let fullContent = ''
let totalTokens = 0
const transformStream = new TransformStream({
async transform(chunk, controller) {
const text = new TextDecoder().decode(chunk)
const lines = text.split('\n').filter(line => line.startsWith('data: '))
for (const line of lines) {
const data = line.slice(6).trim()
if (data === '[DONE]') {
// Save final document
const wordCount = fullContent.split(/\s+/).filter(Boolean).length
await prisma.document.update({
where: { id: document.id },
data: {
content: fullContent,
wordCount,
status: 'COMPLETED',
tokens: totalTokens,
},
})
await prisma.usageLog.create({
data: {
userId: session.user.id,
type: 'DOCUMENT',
tokens: totalTokens,
cost: (totalTokens / 1000) * 0.005,
},
})
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true, documentId: document.id })}\n\n`))
return
}
try {
const parsed = JSON.parse(data)
const content = parsed.choices?.[0]?.delta?.content
if (content) {
fullContent += content
totalTokens += content.length // Approximate
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content, documentId: document.id })}\n\n`))
}
} catch {
// Skip malformed chunks
}
}
},
})
const responseStream = openaiRes.body!.pipeThrough(transformStream)
return new Response(responseStream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
} catch (error) {
console.error('Document generation error:', error)
return new Response(JSON.stringify({ error: 'Erro interno do servidor' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}
function generateMockDocument(type: string, area: string, details: Record<string, string>): string {
const autor = details.autor || '[NOME DO AUTOR]'
const reu = details.reu || '[NOME DO RÉU]'
return `EXCELENTÍSSIMO(A) SENHOR(A) DOUTOR(A) JUIZ(A) DE DIREITO DA ___ VARA CÍVEL DA COMARCA DE [CIDADE] - ESTADO DE [UF]
${autor}, brasileiro(a), [estado civil], [profissão], portador(a) do RG nº [COMPLETAR] e CPF nº [COMPLETAR], residente e domiciliado(a) na [endereço completo], por intermédio de seu(sua) advogado(a) que esta subscreve, com escritório profissional na [endereço do escritório], onde recebe intimações, vem, respeitosamente, perante Vossa Excelência, com fundamento nos artigos 319 e seguintes do Código de Processo Civil (Lei nº 13.105/2015), propor a presente
PETIÇÃO INICIAL
em face de ${reu}, [qualificação completa do réu], pelos fatos e fundamentos a seguir expostos:
I - DOS FATOS
${details.fatos || '1. [Narrar os fatos de forma clara e cronológica, indicando datas, locais e circunstâncias relevantes.]'}
2. Conforme restará demonstrado, os fatos narrados configuram clara violação aos direitos do(a) Autor(a), ensejando a tutela jurisdicional ora pleiteada.
3. Diante da situação narrada, não restou alternativa ao(à) Autor(a) senão buscar a tutela do Poder Judiciário para ver seus direitos resguardados.
II - DO DIREITO
${details.fundamentos || `4. O presente caso encontra amparo no ordenamento jurídico brasileiro, especialmente nos seguintes dispositivos legais:
5. A Constituição Federal de 1988, em seu artigo 5º, inciso XXXV, assegura que "a lei não excluirá da apreciação do Poder Judiciário lesão ou ameaça a direito". Trata-se do princípio da inafastabilidade da jurisdição, que garante ao(à) Autor(a) o direito de buscar a tutela jurisdicional.
6. O Código Civil (Lei nº 10.406/2002), em seu artigo 186, estabelece que "aquele que, por ação ou omissão voluntária, negligência ou imprudência, violar direito e causar dano a outrem, ainda que exclusivamente moral, comete ato ilícito".
7. Nesse sentido, o artigo 927 do mesmo diploma legal determina que "aquele que, por ato ilícito (arts. 186 e 187), causar dano a outrem, fica obrigado a repará-lo".
8. A jurisprudência do Superior Tribunal de Justiça é pacífica nesse sentido:
"CIVIL E PROCESSUAL CIVIL. RESPONSABILIDADE CIVIL. DANO MORAL. CONFIGURAÇÃO. O dano moral prescinde de prova quando decorre de fato por si só lesivo à dignidade da pessoa humana." (STJ, AgRg no AREsp nº XXXXX/SP, Rel. Min. XXXXX, DJe XX/XX/XXXX).`}
III - DOS PEDIDOS
Ante o exposto, requer a Vossa Excelência:
${details.pedidos || `a) A citação do(a) Réu(Ré) para, querendo, apresentar contestação no prazo legal, sob pena de revelia e confissão quanto à matéria de fato;
b) A procedência total dos pedidos formulados nesta inicial;
c) A condenação do(a) Réu(Ré) ao pagamento de indenização por danos morais, em valor a ser arbitrado por Vossa Excelência, observados os princípios da razoabilidade e proporcionalidade;
d) A condenação do(a) Réu(Ré) ao pagamento das custas processuais e honorários advocatícios, nos termos do artigo 85 do CPC;`}
e) A produção de todas as provas admitidas em direito, especialmente documental, testemunhal, pericial e depoimento pessoal do(a) Réu(Ré), sob pena de confesso(a).
Dá-se à causa o valor de R$ [COMPLETAR] ([valor por extenso]).
Termos em que,
Pede deferimento.
[Cidade], [data].
_______________________________
[NOME DO ADVOGADO]
OAB/[UF] nº [COMPLETAR]`
}