359 lines
14 KiB
TypeScript
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]`
|
|
}
|