Initial commit: LexMind - Plataforma Jurídica Inteligente
This commit is contained in:
358
src/app/api/documents/generate/route.ts
Normal file
358
src/app/api/documents/generate/route.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
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]`
|
||||
}
|
||||
Reference in New Issue
Block a user