teqo/src/app/(campaign)/campanha/api/ai-chat/route.ts
Francisco Ignácio Fontoura Solla f9cd1df7b2 feat: AI chat 'Sollinha' — campaign data assistant (v1 read-only)
- Streaming AI endpoint at /campanha/api/ai-chat with DeepSeek V4 Flash
- 8 tools: calculate, getMunicipalityVotes, getTopDeputies, getDobradinhas,
  getMunicipalityOverview, getLeaderships, getOrganizations, searchEntities
- In-memory rate limiter (50 msgs / 15 min per user)
- Desktop: resizable right sidebar via react-resizable-panels
- Mobile: full-screen Drawer opened by FAB (2/3 size of quick-actions FAB)
- Markdown rendering with react-markdown + remark-gfm (tables, bold, lists)
- Session-bound context (clean slate each open)
- Auth via existing campaign-token cookie, tools respect user RBAC
- Architecture doc: docs/plans/ai-chat-sollinha.md
- E2E tests: campaignAiChat.e2e.spec.ts (mocked endpoint)
2026-08-04 06:17:31 -03:00

51 lines
1.4 KiB
TypeScript

import { deepSeek } from '@ai-sdk/deepseek'
import configPromise from '@payload-config'
import {
convertToModelMessages,
createUIMessageStreamResponse,
stepCountIs,
streamText,
toUIMessageStream,
} from 'ai'
import { getPayload } from 'payload'
import { checkRateLimit } from '@/utilities/ai/rateLimit'
import { AI_SYSTEM_PROMPT } from '@/utilities/ai/systemPrompt'
import { buildAITools } from '@/utilities/ai/tools'
import { getCampaignUserRaw } from '@/utilities/campaignAuth'
export const maxDuration = 60
export async function POST(req: Request) {
// Authenticate
const user = await getCampaignUserRaw()
if (!user) {
return Response.json({ error: 'Não autenticado.' }, { status: 401 })
}
// Rate limit
if (!checkRateLimit(user.id)) {
return Response.json(
{
error: 'Você atingiu o limite de mensagens. Aguarde alguns minutos e tente novamente.',
},
{ status: 429 },
)
}
const payload = await getPayload({ config: configPromise })
const { messages } = await req.json()
const result = streamText({
model: deepSeek('deepseek-v4-flash'),
system: AI_SYSTEM_PROMPT,
messages: await convertToModelMessages(messages),
tools: buildAITools({ user, payload }),
stopWhen: stepCountIs(10),
})
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
})
}