voltar ~/portfolio

$ open projects/iaejovem

HackFaesa2025 | saúde emocional estudantil

IAeJovem aproxima apoio emocional da rotina escolar sem expor conversas.

Estudantes conversam com a Ayla, uma IA com tom acolhedor. Professores veem scores e sinais de atenção, mas não leem o conteúdo privado das conversas.

3 perfisaluno, professor e admin
IA empáticaAyla conversa com tom acolhedor
Scores 0-10sinal emocional sem abrir conversas
Auditoriaações sensíveis ficam registradas

$ cat story.md

História

Pensei o IAeJovem como uma ponte cuidadosa entre conversa e acompanhamento escolar. O estudante precisa de um espaço reservado para falar. A escola precisa perceber quando algo merece atenção. Por isso, o projeto separa chat privado, score emocional e painel pedagógico.

$ cat architecture.svg

Arquitetura

Diagrama da arquitetura do IAeJovem com atores, middleware RBAC, painéis, API routes, Abacus AI e camada de dados Prisma PostgreSQL.
Fluxo de dados: o middleware libera rotas conforme o papel do usuário, os painéis chamam API Routes, Ayla conversa via Abacus.AI e a análise emocional fica separada do texto privado.

$ grep selling-points

Por que vale olhar

Privacidade desde o desenho

O professor acompanha score e alerta. A conversa em si continua fora do painel.

Mais que um chat

Tem pontos, resgates, dashboards, notificações, auditoria e ações em massa.

Ayla tem voz própria

A conversa foi pensada para soar acolhedora, jovem e direta, não como formulário disfarçado.

$ view code --tabs

Viewer de código

const systemPrompt = `Você é a Ayla, uma companheira empática e acolhedora que conversa com estudantes para oferecer apoio emocional.
PERSONALIDADE:
- Empática, jovem, acolhedora e compreensiva
- Valida sentimentos sem julgamento
- Faz perguntas abertas para ajudar o estudante a se expressar
DIRETRIZES:
- Use o nome "${firstName}" ocasionalmente de forma natural
- Responda de forma concisa (máximo 3 frases por vez)
- Se o estudante mencionar problemas sérios, sugira buscar ajuda`;

const response = await fetch("https://apps.abacus.ai/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.ABACUSAI_API_KEY}`
  },
  body: JSON.stringify({
    model: "gpt-4.1-mini",
    messages: [{ role: "system", content: systemPrompt }, ...messages],
    stream: true,
  }),
});

const stream = new ReadableStream({
  async start(controller) {
    const reader = response.body?.getReader();
    const decoder = new TextDecoder();
    /* ... lógica do chunk reader ... */
  }
});
function analyzeEmotions(messages: ConversationMessage[]): { score: number, emocao: string } {
  const emotionWords = {
    feliz: ["bem", "bom", "ótimo", "feliz", "alegre"],
    triste: ["triste", "mal", "ruim", "deprimido", "sozinho"],
    ansioso: ["ansioso", "preocupado", "estressado", "nervoso"],
  };

  let positiveCount = 0;
  let negativeCount = 0;

  userMessages.forEach((message) => {
    const words = message.content.toLowerCase().split(/\s+/);
    words.forEach((word) => {
      Object.entries(emotionWords).forEach(([emotion, keywords]) => {
        if (keywords.some((keyword) => word.includes(keyword))) {
          if (emotion === "feliz" || emotion === "tranquilo") positiveCount++;
          else negativeCount++;
        }
      });
    });
  });

  const totalEmotionalWords = positiveCount + negativeCount;
  const sentimentoScore = totalEmotionalWords > 0
    ? (positiveCount - negativeCount) / totalEmotionalWords
    : 0;
  const baseScore = 5 + (sentimentoScore * 5);

  return {
    score: Math.round(Math.max(0, Math.min(10, baseScore)) * 10) / 10,
    emocao: "determinado_pelo_algoritmo"
  };
}
const code = `IAJ-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;

const redemption = await prisma.$transaction(async (tx) => {
  const redemption = await tx.redemption.create({
    data: { userId: user.id, productId: product.id, code, status: "AGUARDANDO_RETIRADA" }
  });

  await tx.user.update({
    where: { id: user.id },
    data: { points: { decrement: product.pointsCost } }
  });

  await tx.product.update({
    where: { id: product.id },
    data: { stock: { decrement: 1 } }
  });

  await tx.notification.create({
    data: {
      userId: user.id,
      message: `Parabéns! Você resgatou: ${product.name}. Código de retirada: ${code}`,
    }
  });

  return redemption;
});
export default withAuth(
  function middleware(request) {
    const { pathname } = request.nextUrl;
    const token = request.nextauth.token;
    const userRole = token.role;

    if (pathname.startsWith("/aluno") && userRole !== "ALUNO") {
      return NextResponse.redirect(new URL("/", request.url));
    }
    if (pathname.startsWith("/professor") && userRole !== "PROFESSOR") {
      return NextResponse.redirect(new URL("/", request.url));
    }
    if (pathname.startsWith("/admin") && userRole !== "ADMINISTRADOR") {
      return NextResponse.redirect(new URL("/", request.url));
    }

    return NextResponse.next();
  }
);
export default function AlunoHomePage() {
  const { data: session } = useSession()
  const firstName = session?.user?.name?.split(" ")[0] || "Estudante"

  return (
    <Card className="border-0 shadow-lg bg-gradient-to-br from-blue-50 to-blue-100 hover:shadow-xl transition-all duration-300 group">
      <CardHeader className="pb-4">
        <div className="flex items-center justify-between">
          <div className="w-12 h-12 rounded-full bg-iaj-blue flex items-center justify-center group-hover:scale-110 transition-transform">
            <MessageCircle className="w-6 h-6 text-white" />
          </div>
          <ArrowRight className="w-5 h-5 text-iaj-blue group-hover:translate-x-1 transition-transform" />
        </div>
      </CardHeader>
      <CardContent className="pt-0">
        <h3 className="font-bold text-lg text-gray-800 mb-2">Conversar com Ayla</h3>
        <p className="text-gray-600 text-sm mb-4">
          Tenha uma conversa acolhedora e ganhe pontos cuidando do seu bem-estar.
        </p>
        <Link href="/aluno/conversar">
          <button className="w-full bg-iaj-blue hover:bg-blue-600 rounded p-2 text-white font-semibold">
            Começar conversa
          </button>
        </Link>
      </CardContent>
    </Card>
  )
}

$ result

Um bom exemplo de IA em produto sensível: útil, mas com limite claro para privacidade.