cidadao.ai-backend / docs /development /FRONTEND_INTEGRATION_GUIDE.md
anderson-ufrj
refactor: complete repository reorganization and documentation update
92d464e
# 🚀 Guia Completo de Integração Frontend - Cidadão.AI Backend API
**Versão**: 1.0.0
**Última Atualização**: Janeiro 2025
**Backend URL**: https://neural-thinker-cidadao-ai-backend.hf.space/
**Documentação Interativa**: https://neural-thinker-cidadao-ai-backend.hf.space/docs
## 📋 Índice
1. [Visão Geral](#visão-geral)
2. [Configuração Inicial](#configuração-inicial)
3. [Autenticação](#autenticação)
4. [Endpoints Principais](#endpoints-principais)
5. [WebSockets e Real-time](#websockets-e-real-time)
6. [Exemplos de Implementação](#exemplos-de-implementação)
7. [TypeScript Interfaces](#typescript-interfaces)
8. [Tratamento de Erros](#tratamento-de-erros)
9. [Rate Limiting](#rate-limiting)
10. [Boas Práticas](#boas-práticas)
---
## 🎯 Visão Geral
O Cidadão.AI é uma plataforma multi-agente de IA para análise de transparência governamental brasileira. O backend fornece APIs RESTful, WebSockets e Server-Sent Events (SSE) para comunicação em tempo real.
### Características Principais
- **Autenticação JWT** com refresh tokens
- **Rate Limiting** por tiers (Free, Basic, Premium, Enterprise)
- **WebSockets** para comunicação bidirecional
- **SSE** para streaming de respostas
- **17 Agentes de IA** especializados com identidades brasileiras
- **Cache otimizado** com hit rate >90%
- **Tempo de resposta** <2s para agentes
### Base URLs
```typescript
// Produção
const API_BASE_URL = 'https://neural-thinker-cidadao-ai-backend.hf.space'
// Development (local)
const API_BASE_URL_DEV = 'http://localhost:8000'
// WebSocket
const WS_BASE_URL = 'wss://neural-thinker-cidadao-ai-backend.hf.space'
const WS_BASE_URL_DEV = 'ws://localhost:8000'
```
---
## 🔧 Configuração Inicial
### 1. Instalação de Dependências
```bash
# Axios para requisições HTTP
npm install axios
# Socket.io para WebSockets (opcional - pode usar native WebSocket)
npm install socket.io-client
# Event Source Polyfill para SSE
npm install eventsource
# TypeScript types
npm install -D @types/eventsource
```
### 2. Configuração do Cliente HTTP
```typescript
// utils/api-client.ts
import axios, { AxiosInstance } from 'axios'
class ApiClient {
private client: AxiosInstance
private refreshingToken: Promise<string> | null = null
constructor() {
this.client = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'https://neural-thinker-cidadao-ai-backend.hf.space',
headers: {
'Content-Type': 'application/json',
},
timeout: 30000, // 30 segundos
})
// Request interceptor para adicionar token
this.client.interceptors.request.use(
async (config) => {
const token = this.getAccessToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => Promise.reject(error)
)
// Response interceptor para refresh token
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
await this.refreshToken()
const newToken = this.getAccessToken()
originalRequest.headers.Authorization = `Bearer ${newToken}`
return this.client(originalRequest)
} catch (refreshError) {
// Redirecionar para login
window.location.href = '/login'
return Promise.reject(refreshError)
}
}
return Promise.reject(error)
}
)
}
private getAccessToken(): string | null {
return localStorage.getItem('access_token')
}
private getRefreshToken(): string | null {
return localStorage.getItem('refresh_token')
}
private async refreshToken(): Promise<string> {
if (this.refreshingToken) {
return this.refreshingToken
}
this.refreshingToken = this.client
.post('/auth/refresh', {
refresh_token: this.getRefreshToken(),
})
.then((response) => {
const { access_token, refresh_token } = response.data
localStorage.setItem('access_token', access_token)
localStorage.setItem('refresh_token', refresh_token)
this.refreshingToken = null
return access_token
})
.catch((error) => {
this.refreshingToken = null
throw error
})
return this.refreshingToken
}
// Métodos públicos
async get<T>(url: string, config?: any): Promise<T> {
const response = await this.client.get<T>(url, config)
return response.data
}
async post<T>(url: string, data?: any, config?: any): Promise<T> {
const response = await this.client.post<T>(url, data, config)
return response.data
}
async put<T>(url: string, data?: any, config?: any): Promise<T> {
const response = await this.client.put<T>(url, data, config)
return response.data
}
async delete<T>(url: string, config?: any): Promise<T> {
const response = await this.client.delete<T>(url, config)
return response.data
}
}
export const apiClient = new ApiClient()
```
---
## 🔐 Autenticação
### Fluxo de Autenticação
1. **Login** → Recebe access_token e refresh_token
2. **Armazenamento** → Salvar tokens no localStorage/cookies seguros
3. **Uso** → Enviar access_token no header Authorization
4. **Refresh** → Quando access_token expira, usar refresh_token
5. **Logout** → Limpar tokens e chamar endpoint de logout
### Endpoints de Autenticação
#### 1. Login
```typescript
// POST /auth/login
interface LoginRequest {
email: string
password: string
}
interface LoginResponse {
access_token: string
refresh_token: string
token_type: string
expires_in: number
user: {
id: string
email: string
name: string
role: string
is_active: boolean
}
}
// Exemplo de uso
async function login(email: string, password: string): Promise<LoginResponse> {
const response = await apiClient.post<LoginResponse>('/auth/login', {
email,
password
})
// Salvar tokens
localStorage.setItem('access_token', response.access_token)
localStorage.setItem('refresh_token', response.refresh_token)
localStorage.setItem('user', JSON.stringify(response.user))
return response
}
```
#### 2. Refresh Token
```typescript
// POST /auth/refresh
interface RefreshRequest {
refresh_token: string
}
interface RefreshResponse {
access_token: string
refresh_token: string
token_type: string
expires_in: number
}
async function refreshAccessToken(): Promise<RefreshResponse> {
const refreshToken = localStorage.getItem('refresh_token')
const response = await apiClient.post<RefreshResponse>('/auth/refresh', {
refresh_token: refreshToken
})
// Atualizar tokens
localStorage.setItem('access_token', response.access_token)
localStorage.setItem('refresh_token', response.refresh_token)
return response
}
```
#### 3. Logout
```typescript
// POST /auth/logout
async function logout(): Promise<void> {
try {
await apiClient.post('/auth/logout')
} finally {
// Limpar dados locais
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
localStorage.removeItem('user')
// Redirecionar para login
window.location.href = '/login'
}
}
```
#### 4. Get Current User
```typescript
// GET /auth/me
interface UserInfo {
id: string
email: string
name: string
role: string
is_active: boolean
created_at: string
last_login: string
}
async function getCurrentUser(): Promise<UserInfo> {
return await apiClient.get<UserInfo>('/auth/me')
}
```
---
## 📡 Endpoints Principais
### 1. Chat API
#### Enviar Mensagem
```typescript
// POST /api/v1/chat/message
interface ChatMessageRequest {
message: string
session_id?: string // Opcional, será criado se não fornecido
context?: Record<string, any>
}
interface ChatMessageResponse {
response: string
session_id: string
message_id: string
agent_used: string
processing_time: number
suggestions?: string[]
}
async function sendChatMessage(message: string, sessionId?: string): Promise<ChatMessageResponse> {
return await apiClient.post<ChatMessageResponse>('/api/v1/chat/message', {
message,
session_id: sessionId
})
}
```
#### Stream de Resposta (SSE)
```typescript
// POST /api/v1/chat/stream
interface StreamToken {
type: 'token' | 'error' | 'complete'
content?: string
error?: string
metadata?: {
agent: string
processing_time?: number
}
}
function streamChatMessage(message: string, sessionId?: string): EventSource {
const token = localStorage.getItem('access_token')
const url = `${API_BASE_URL}/api/v1/chat/stream?token=${token}`
const eventSource = new EventSource(url, {
headers: {
'Content-Type': 'application/json',
}
})
// Enviar mensagem inicial
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
message,
session_id: sessionId
})
})
return eventSource
}
// Uso do SSE
const eventSource = streamChatMessage('Analise os contratos de 2024', sessionId)
eventSource.onmessage = (event) => {
const data: StreamToken = JSON.parse(event.data)
switch (data.type) {
case 'token':
// Adicionar token à resposta
setResponse(prev => prev + data.content)
break
case 'complete':
// Resposta completa
eventSource.close()
break
case 'error':
console.error('Stream error:', data.error)
eventSource.close()
break
}
}
eventSource.onerror = (error) => {
console.error('SSE Error:', error)
eventSource.close()
}
```
#### Histórico de Chat
```typescript
// GET /api/v1/chat/history/{session_id}/paginated
interface PaginatedChatHistory {
messages: ChatMessage[]
total: number
page: number
page_size: number
has_next: boolean
has_previous: boolean
}
interface ChatMessage {
id: string
role: 'user' | 'assistant'
content: string
timestamp: string
agent_used?: string
metadata?: Record<string, any>
}
async function getChatHistory(
sessionId: string,
page: number = 1,
pageSize: number = 20
): Promise<PaginatedChatHistory> {
return await apiClient.get<PaginatedChatHistory>(
`/api/v1/chat/history/${sessionId}/paginated`,
{
params: { page, page_size: pageSize }
}
)
}
```
#### Sugestões Rápidas
```typescript
// GET /api/v1/chat/suggestions
interface ChatSuggestion {
id: string
text: string
category: 'investigation' | 'analysis' | 'report' | 'general'
icon?: string
}
async function getChatSuggestions(context?: string): Promise<ChatSuggestion[]> {
return await apiClient.get<ChatSuggestion[]>('/api/v1/chat/suggestions', {
params: { context }
})
}
```
### 2. Agentes de IA
#### Zumbi dos Palmares - Detecção de Anomalias
```typescript
// POST /api/v1/agents/zumbi
interface ZumbiRequest {
data: {
contract_id?: string
vendor_name?: string
amount?: number
date?: string
description?: string
[key: string]: any
}
analysis_type?: 'full' | 'quick' | 'pattern'
}
interface AnomalyResult {
anomaly_score: number // 0-1
anomaly_type: string[]
confidence: number
severity: 'low' | 'medium' | 'high' | 'critical'
details: {
statistical_analysis: Record<string, any>
pattern_analysis: Record<string, any>
spectral_analysis?: Record<string, any>
}
recommendations: string[]
visualizations?: {
type: string
data: any
}[]
}
async function detectAnomalies(data: any): Promise<AnomalyResult> {
return await apiClient.post<AnomalyResult>('/api/v1/agents/zumbi', {
data,
analysis_type: 'full'
})
}
```
#### Status dos Agentes
```typescript
// GET /api/v1/agents/status
interface AgentStatus {
agent_id: string
name: string
status: 'idle' | 'processing' | 'error' | 'maintenance'
health: {
cpu_usage: number
memory_usage: number
response_time_ms: number
success_rate: number
}
capabilities: string[]
last_active: string
}
async function getAgentsStatus(): Promise<AgentStatus[]> {
return await apiClient.get<AgentStatus[]>('/api/v1/agents/status')
}
```
### 3. Investigações
#### Iniciar Investigação
```typescript
// POST /api/v1/investigations/start
interface StartInvestigationRequest {
title: string
description: string
type: 'contract' | 'vendor' | 'pattern' | 'general'
parameters: {
date_range?: {
start: string
end: string
}
vendor_ids?: string[]
contract_ids?: string[]
amount_range?: {
min: number
max: number
}
keywords?: string[]
[key: string]: any
}
agents?: string[] // Agentes específicos para usar
priority?: 'low' | 'medium' | 'high' | 'critical'
}
interface Investigation {
id: string
title: string
status: 'pending' | 'running' | 'completed' | 'failed'
progress: number // 0-100
created_at: string
updated_at: string
estimated_completion: string
results?: InvestigationResults
}
async function startInvestigation(
request: StartInvestigationRequest
): Promise<Investigation> {
return await apiClient.post<Investigation>('/api/v1/investigations/start', request)
}
```
#### Acompanhar Investigação
```typescript
// GET /api/v1/investigations/{id}
async function getInvestigation(id: string): Promise<Investigation> {
return await apiClient.get<Investigation>(`/api/v1/investigations/${id}`)
}
// GET /api/v1/investigations/{id}/results
interface InvestigationResults {
summary: string
findings: Finding[]
anomalies: AnomalyResult[]
patterns: Pattern[]
recommendations: string[]
risk_score: number
confidence: number
visualizations: Visualization[]
raw_data?: any
}
interface Finding {
id: string
type: string
description: string
severity: 'low' | 'medium' | 'high' | 'critical'
evidence: any[]
agent: string
}
async function getInvestigationResults(id: string): Promise<InvestigationResults> {
return await apiClient.get<InvestigationResults>(`/api/v1/investigations/${id}/results`)
}
```
### 4. Análises
#### Análise de Padrões
```typescript
// POST /api/v1/analysis/patterns
interface PatternAnalysisRequest {
data: any[]
analysis_config?: {
min_support?: number
min_confidence?: number
algorithms?: ('apriori' | 'fpgrowth' | 'eclat')[]
}
time_range?: {
start: string
end: string
}
}
interface PatternAnalysisResponse {
patterns: Pattern[]
statistics: {
total_patterns: number
avg_confidence: number
processing_time: number
}
visualizations: Visualization[]
}
interface Pattern {
id: string
pattern: string[]
support: number
confidence: number
lift: number
occurrences: number
examples: any[]
}
```
#### Análise de Tendências
```typescript
// POST /api/v1/analysis/trends
interface TrendAnalysisRequest {
metric: string
data: {
timestamp: string
value: number
metadata?: any
}[]
analysis_type: 'linear' | 'seasonal' | 'polynomial' | 'all'
forecast_periods?: number
}
interface TrendAnalysisResponse {
current_trend: 'increasing' | 'decreasing' | 'stable'
trend_strength: number
forecast: {
timestamp: string
value: number
confidence_interval: {
lower: number
upper: number
}
}[]
seasonality?: {
period: string
strength: number
}
change_points: {
timestamp: string
significance: number
}[]
}
```
### 5. Relatórios
#### Gerar Relatório
```typescript
// POST /api/v1/reports/generate
interface GenerateReportRequest {
investigation_id?: string
template: 'executive_summary' | 'detailed' | 'technical' | 'compliance'
format: 'pdf' | 'html' | 'markdown' | 'docx'
sections?: string[]
include_visualizations?: boolean
language?: 'pt-BR' | 'en-US'
}
interface Report {
id: string
title: string
status: 'generating' | 'ready' | 'failed'
format: string
size_bytes: number
download_url?: string
preview_url?: string
created_at: string
expires_at: string
}
async function generateReport(request: GenerateReportRequest): Promise<Report> {
return await apiClient.post<Report>('/api/v1/reports/generate', request)
}
```
#### Download Relatório
```typescript
// GET /api/v1/reports/{id}/download
async function downloadReport(reportId: string): Promise<Blob> {
const response = await apiClient.get(`/api/v1/reports/${reportId}/download`, {
responseType: 'blob'
})
return response
}
// Exemplo de uso
const blob = await downloadReport(reportId)
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `relatorio-${reportId}.pdf`
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
```
---
## 🔄 WebSockets e Real-time
### Configuração do WebSocket Client
```typescript
// utils/websocket-client.ts
export class WebSocketClient {
private ws: WebSocket | null = null
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 1000
private heartbeatInterval: NodeJS.Timeout | null = null
private eventHandlers: Map<string, Set<Function>> = new Map()
constructor(private baseUrl: string) {}
connect(endpoint: string, token: string): Promise<void> {
return new Promise((resolve, reject) => {
const url = `${this.baseUrl}${endpoint}?token=${token}`
try {
this.ws = new WebSocket(url)
this.ws.onopen = () => {
console.log('WebSocket connected')
this.reconnectAttempts = 0
this.startHeartbeat()
resolve()
}
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
this.handleMessage(data)
} catch (error) {
console.error('Failed to parse WebSocket message:', error)
}
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error)
reject(error)
}
this.ws.onclose = () => {
console.log('WebSocket disconnected')
this.stopHeartbeat()
this.handleReconnect(endpoint, token)
}
} catch (error) {
reject(error)
}
})
}
private handleMessage(data: any) {
const { type, payload } = data
const handlers = this.eventHandlers.get(type)
if (handlers) {
handlers.forEach(handler => handler(payload))
}
// Handler global
const globalHandlers = this.eventHandlers.get('*')
if (globalHandlers) {
globalHandlers.forEach(handler => handler(data))
}
}
on(event: string, handler: Function) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, new Set())
}
this.eventHandlers.get(event)!.add(handler)
}
off(event: string, handler: Function) {
const handlers = this.eventHandlers.get(event)
if (handlers) {
handlers.delete(handler)
}
}
send(type: string, payload: any) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type, payload }))
} else {
console.error('WebSocket is not connected')
}
}
private startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
this.send('ping', { timestamp: Date.now() })
}, 30000) // 30 segundos
}
private stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval)
this.heartbeatInterval = null
}
}
private handleReconnect(endpoint: string, token: string) {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
console.log(`Reconnecting in ${delay}ms... (attempt ${this.reconnectAttempts})`)
setTimeout(() => {
this.connect(endpoint, token)
}, delay)
} else {
console.error('Max reconnection attempts reached')
}
}
disconnect() {
if (this.ws) {
this.ws.close()
this.ws = null
}
this.stopHeartbeat()
}
}
```
### WebSocket para Chat
```typescript
// hooks/useWebSocketChat.ts
import { useEffect, useRef, useState } from 'react'
import { WebSocketClient } from '@/utils/websocket-client'
interface WebSocketMessage {
type: 'message' | 'typing' | 'user_joined' | 'user_left' | 'error'
payload: any
}
export function useWebSocketChat(sessionId: string) {
const [messages, setMessages] = useState<any[]>([])
const [isConnected, setIsConnected] = useState(false)
const [typingUsers, setTypingUsers] = useState<string[]>([])
const wsClient = useRef<WebSocketClient>()
useEffect(() => {
const token = localStorage.getItem('access_token')
if (!token || !sessionId) return
wsClient.current = new WebSocketClient(
process.env.NEXT_PUBLIC_WS_URL || 'wss://neural-thinker-cidadao-ai-backend.hf.space'
)
// Conectar ao WebSocket
wsClient.current.connect(`/api/v1/ws/chat/${sessionId}`, token)
.then(() => {
setIsConnected(true)
})
.catch((error) => {
console.error('Failed to connect to WebSocket:', error)
})
// Configurar event handlers
wsClient.current.on('message', (payload: any) => {
setMessages(prev => [...prev, payload])
})
wsClient.current.on('typing', (payload: { user_id: string, is_typing: boolean }) => {
setTypingUsers(prev => {
if (payload.is_typing) {
return [...prev, payload.user_id]
} else {
return prev.filter(id => id !== payload.user_id)
}
})
})
wsClient.current.on('error', (payload: any) => {
console.error('WebSocket error:', payload)
})
// Cleanup
return () => {
if (wsClient.current) {
wsClient.current.disconnect()
}
}
}, [sessionId])
const sendMessage = (message: string) => {
if (wsClient.current && isConnected) {
wsClient.current.send('message', {
content: message,
timestamp: new Date().toISOString()
})
}
}
const sendTypingIndicator = (isTyping: boolean) => {
if (wsClient.current && isConnected) {
wsClient.current.send('typing', { is_typing: isTyping })
}
}
return {
messages,
isConnected,
typingUsers,
sendMessage,
sendTypingIndicator
}
}
```
### WebSocket para Investigações
```typescript
// hooks/useInvestigationWebSocket.ts
export function useInvestigationWebSocket(investigationId: string) {
const [status, setStatus] = useState<string>('pending')
const [progress, setProgress] = useState(0)
const [findings, setFindings] = useState<any[]>([])
const [logs, setLogs] = useState<string[]>([])
useEffect(() => {
if (!investigationId) return
const token = localStorage.getItem('access_token')
const wsClient = new WebSocketClient(process.env.NEXT_PUBLIC_WS_URL!)
wsClient.connect(`/api/v1/ws/investigations/${investigationId}`, token!)
.then(() => {
console.log('Connected to investigation WebSocket')
})
wsClient.on('status_update', (payload: { status: string, progress: number }) => {
setStatus(payload.status)
setProgress(payload.progress)
})
wsClient.on('finding', (payload: any) => {
setFindings(prev => [...prev, payload])
})
wsClient.on('log', (payload: { message: string, level: string }) => {
setLogs(prev => [...prev, `[${payload.level}] ${payload.message}`])
})
wsClient.on('complete', (payload: { results: any }) => {
setStatus('completed')
setProgress(100)
// Processar resultados finais
})
return () => {
wsClient.disconnect()
}
}, [investigationId])
return { status, progress, findings, logs }
}
```
---
## 💻 Exemplos de Implementação
### 1. Componente de Chat Completo
```typescript
// components/Chat/ChatInterface.tsx
import React, { useState, useRef, useEffect } from 'react'
import { useWebSocketChat } from '@/hooks/useWebSocketChat'
import { apiClient } from '@/utils/api-client'
interface ChatInterfaceProps {
sessionId?: string
}
export function ChatInterface({ sessionId: initialSessionId }: ChatInterfaceProps) {
const [sessionId, setSessionId] = useState(initialSessionId || '')
const [messages, setMessages] = useState<any[]>([])
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isStreaming, setIsStreaming] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const {
messages: wsMessages,
isConnected,
sendMessage: wsSendMessage,
sendTypingIndicator
} = useWebSocketChat(sessionId)
// Carregar histórico ao montar
useEffect(() => {
if (sessionId) {
loadChatHistory()
}
}, [sessionId])
// Scroll automático
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages, wsMessages])
const loadChatHistory = async () => {
try {
const history = await apiClient.get(
`/api/v1/chat/history/${sessionId}/paginated`
)
setMessages(history.messages)
} catch (error) {
console.error('Failed to load chat history:', error)
}
}
const sendMessage = async () => {
if (!input.trim()) return
const userMessage = {
role: 'user',
content: input,
timestamp: new Date().toISOString()
}
setMessages(prev => [...prev, userMessage])
setInput('')
setIsLoading(true)
try {
// Se WebSocket conectado, usar WebSocket
if (isConnected) {
wsSendMessage(input)
} else {
// Senão, usar API REST
const response = await apiClient.post('/api/v1/chat/message', {
message: input,
session_id: sessionId
})
if (!sessionId) {
setSessionId(response.session_id)
}
setMessages(prev => [...prev, {
role: 'assistant',
content: response.response,
agent_used: response.agent_used,
timestamp: new Date().toISOString()
}])
}
} catch (error) {
console.error('Failed to send message:', error)
// Mostrar erro ao usuário
} finally {
setIsLoading(false)
}
}
const streamMessage = () => {
if (!input.trim()) return
const userMessage = {
role: 'user',
content: input,
timestamp: new Date().toISOString()
}
setMessages(prev => [...prev, userMessage])
setInput('')
setIsStreaming(true)
let assistantMessage = {
role: 'assistant',
content: '',
timestamp: new Date().toISOString(),
isStreaming: true
}
setMessages(prev => [...prev, assistantMessage])
// Usar SSE para streaming
const eventSource = streamChatMessage(input, sessionId)
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data)
if (data.type === 'token') {
assistantMessage.content += data.content
setMessages(prev => {
const newMessages = [...prev]
newMessages[newMessages.length - 1] = { ...assistantMessage }
return newMessages
})
} else if (data.type === 'complete') {
assistantMessage.isStreaming = false
assistantMessage.agent_used = data.metadata?.agent
setMessages(prev => {
const newMessages = [...prev]
newMessages[newMessages.length - 1] = { ...assistantMessage }
return newMessages
})
setIsStreaming(false)
eventSource.close()
}
}
eventSource.onerror = () => {
setIsStreaming(false)
eventSource.close()
}
}
return (
<div className="chat-container">
<div className="messages-container">
{messages.map((message, index) => (
<MessageComponent key={index} message={message} />
))}
{isLoading && <LoadingIndicator />}
<div ref={messagesEndRef} />
</div>
<div className="input-container">
<input
type="text"
value={input}
onChange={(e) => {
setInput(e.target.value)
sendTypingIndicator(true)
}}
onBlur={() => sendTypingIndicator(false)}
onKeyPress={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
sendMessage()
}
}}
placeholder="Digite sua mensagem..."
disabled={isLoading || isStreaming}
/>
<button
onClick={sendMessage}
disabled={isLoading || isStreaming || !input.trim()}
>
Enviar
</button>
<button
onClick={streamMessage}
disabled={isLoading || isStreaming || !input.trim()}
>
Stream
</button>
</div>
{isConnected && (
<div className="connection-status">
<span className="status-dot online" />
Conectado
</div>
)}
</div>
)
}
```
### 2. Dashboard de Investigações
```typescript
// components/Investigations/InvestigationsDashboard.tsx
import React, { useState, useEffect } from 'react'
import { useInvestigationWebSocket } from '@/hooks/useInvestigationWebSocket'
import { apiClient } from '@/utils/api-client'
export function InvestigationsDashboard() {
const [investigations, setInvestigations] = useState<any[]>([])
const [selectedInvestigation, setSelectedInvestigation] = useState<string>('')
const [isCreating, setIsCreating] = useState(false)
// Carregar investigações
useEffect(() => {
loadInvestigations()
}, [])
const loadInvestigations = async () => {
try {
const data = await apiClient.get('/api/v1/investigations/history')
setInvestigations(data)
} catch (error) {
console.error('Failed to load investigations:', error)
}
}
const createInvestigation = async (data: any) => {
setIsCreating(true)
try {
const investigation = await apiClient.post('/api/v1/investigations/start', {
title: data.title,
description: data.description,
type: data.type,
parameters: data.parameters,
priority: 'high'
})
setInvestigations(prev => [investigation, ...prev])
setSelectedInvestigation(investigation.id)
} catch (error) {
console.error('Failed to create investigation:', error)
} finally {
setIsCreating(false)
}
}
return (
<div className="investigations-dashboard">
<div className="investigations-list">
<h2>Investigações</h2>
<button onClick={() => setIsCreating(true)}>
Nova Investigação
</button>
{investigations.map(inv => (
<InvestigationCard
key={inv.id}
investigation={inv}
isSelected={inv.id === selectedInvestigation}
onClick={() => setSelectedInvestigation(inv.id)}
/>
))}
</div>
<div className="investigation-detail">
{selectedInvestigation && (
<InvestigationDetail investigationId={selectedInvestigation} />
)}
</div>
{isCreating && (
<CreateInvestigationModal
onClose={() => setIsCreating(false)}
onCreate={createInvestigation}
/>
)}
</div>
)
}
// Componente de detalhe com WebSocket
function InvestigationDetail({ investigationId }: { investigationId: string }) {
const { status, progress, findings, logs } = useInvestigationWebSocket(investigationId)
const [results, setResults] = useState<any>(null)
useEffect(() => {
if (status === 'completed') {
loadResults()
}
}, [status])
const loadResults = async () => {
try {
const data = await apiClient.get(
`/api/v1/investigations/${investigationId}/results`
)
setResults(data)
} catch (error) {
console.error('Failed to load results:', error)
}
}
return (
<div className="investigation-detail-container">
<div className="status-header">
<h3>Status: {status}</h3>
<ProgressBar value={progress} />
</div>
{status === 'running' && (
<>
<div className="findings-section">
<h4>Descobertas ({findings.length})</h4>
{findings.map((finding, i) => (
<FindingCard key={i} finding={finding} />
))}
</div>
<div className="logs-section">
<h4>Logs</h4>
<div className="logs-container">
{logs.map((log, i) => (
<div key={i} className="log-entry">{log}</div>
))}
</div>
</div>
</>
)}
{status === 'completed' && results && (
<InvestigationResults results={results} />
)}
</div>
)
}
```
### 3. Hook para Análise de Dados
```typescript
// hooks/useDataAnalysis.ts
import { useState } from 'react'
import { apiClient } from '@/utils/api-client'
export function useDataAnalysis() {
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [results, setResults] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
const analyzePatterns = async (data: any[], config?: any) => {
setIsAnalyzing(true)
setError(null)
try {
const response = await apiClient.post('/api/v1/analysis/patterns', {
data,
analysis_config: config
})
setResults(response)
return response
} catch (err: any) {
setError(err.message)
throw err
} finally {
setIsAnalyzing(false)
}
}
const analyzeTrends = async (metric: string, data: any[], options?: any) => {
setIsAnalyzing(true)
setError(null)
try {
const response = await apiClient.post('/api/v1/analysis/trends', {
metric,
data,
...options
})
setResults(response)
return response
} catch (err: any) {
setError(err.message)
throw err
} finally {
setIsAnalyzing(false)
}
}
const detectAnomalies = async (data: any) => {
setIsAnalyzing(true)
setError(null)
try {
const response = await apiClient.post('/api/v1/agents/zumbi', {
data,
analysis_type: 'full'
})
setResults(response)
return response
} catch (err: any) {
setError(err.message)
throw err
} finally {
setIsAnalyzing(false)
}
}
return {
isAnalyzing,
results,
error,
analyzePatterns,
analyzeTrends,
detectAnomalies
}
}
```
---
## 📝 TypeScript Interfaces
### Interfaces Principais
```typescript
// types/api.ts
// Base Types
export interface ApiResponse<T> {
data: T
status: number
message?: string
error?: string
}
export interface PaginatedResponse<T> {
items: T[]
total: number
page: number
page_size: number
has_next: boolean
has_previous: boolean
}
// User & Auth
export interface User {
id: string
email: string
name: string
role: 'admin' | 'user' | 'viewer'
is_active: boolean
created_at: string
last_login?: string
}
export interface AuthTokens {
access_token: string
refresh_token: string
token_type: string
expires_in: number
}
// Chat Types
export interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'system'
content: string
timestamp: string
session_id: string
agent_used?: string
metadata?: Record<string, any>
attachments?: Attachment[]
}
export interface ChatSession {
id: string
user_id: string
created_at: string
updated_at: string
message_count: number
is_active: boolean
context?: Record<string, any>
}
// Agent Types
export interface Agent {
id: string
name: string
type: string
description: string
capabilities: string[]
status: AgentStatus
performance_metrics: AgentMetrics
}
export type AgentStatus = 'idle' | 'processing' | 'error' | 'maintenance'
export interface AgentMetrics {
avg_response_time: number
success_rate: number
total_requests: number
last_24h_requests: number
}
// Investigation Types
export interface Investigation {
id: string
title: string
description: string
type: InvestigationType
status: InvestigationStatus
priority: Priority
progress: number
user_id: string
created_at: string
updated_at: string
started_at?: string
completed_at?: string
estimated_completion?: string
results?: InvestigationResults
error?: string
}
export type InvestigationType = 'contract' | 'vendor' | 'pattern' | 'general'
export type InvestigationStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
export type Priority = 'low' | 'medium' | 'high' | 'critical'
export interface InvestigationResults {
summary: string
findings: Finding[]
anomalies: Anomaly[]
patterns: Pattern[]
recommendations: string[]
risk_score: number
confidence: number
processing_time: number
agents_used: string[]
visualizations?: Visualization[]
}
export interface Finding {
id: string
type: string
title: string
description: string
severity: Severity
confidence: number
evidence: Evidence[]
detected_by: string
detected_at: string
}
export type Severity = 'low' | 'medium' | 'high' | 'critical'
export interface Evidence {
type: string
source: string
data: any
relevance: number
}
// Analysis Types
export interface Anomaly {
id: string
type: string
description: string
anomaly_score: number
severity: Severity
affected_items: any[]
detection_method: string
statistical_significance: number
context: Record<string, any>
}
export interface Pattern {
id: string
pattern: string[]
type: string
support: number
confidence: number
lift: number
occurrences: number
first_seen: string
last_seen: string
examples: any[]
}
export interface Trend {
metric: string
direction: 'increasing' | 'decreasing' | 'stable'
strength: number
change_rate: number
forecast: ForecastPoint[]
seasonality?: Seasonality
change_points: ChangePoint[]
}
export interface ForecastPoint {
timestamp: string
value: number
lower_bound: number
upper_bound: number
confidence: number
}
export interface Seasonality {
period: string
strength: number
pattern: number[]
}
export interface ChangePoint {
timestamp: string
significance: number
direction: 'up' | 'down'
}
// Report Types
export interface Report {
id: string
title: string
type: ReportType
format: ReportFormat
status: ReportStatus
size_bytes: number
pages?: number
download_url?: string
preview_url?: string
created_at: string
expires_at: string
metadata: Record<string, any>
}
export type ReportType = 'executive_summary' | 'detailed' | 'technical' | 'compliance'
export type ReportFormat = 'pdf' | 'html' | 'markdown' | 'docx'
export type ReportStatus = 'generating' | 'ready' | 'failed' | 'expired'
// Visualization Types
export interface Visualization {
id: string
type: VisualizationType
title: string
description?: string
data: any
config: VisualizationConfig
interactive: boolean
}
export type VisualizationType =
| 'line_chart'
| 'bar_chart'
| 'pie_chart'
| 'scatter_plot'
| 'heatmap'
| 'network_graph'
| 'timeline'
| 'geographic_map'
export interface VisualizationConfig {
width?: number
height?: number
colors?: string[]
theme?: 'light' | 'dark'
[key: string]: any
}
// Notification Types
export interface Notification {
id: string
type: NotificationType
title: string
message: string
severity: Severity
read: boolean
created_at: string
data?: any
action_url?: string
}
export type NotificationType =
| 'anomaly_detected'
| 'investigation_complete'
| 'report_ready'
| 'system_alert'
| 'agent_update'
// WebSocket Event Types
export interface WebSocketEvent<T = any> {
type: string
payload: T
timestamp: string
correlation_id?: string
}
export interface ChatWebSocketEvent extends WebSocketEvent {
type: 'message' | 'typing' | 'user_joined' | 'user_left' | 'error'
}
export interface InvestigationWebSocketEvent extends WebSocketEvent {
type: 'status_update' | 'progress' | 'finding' | 'log' | 'complete' | 'error'
}
// Error Types
export interface ApiError {
error: string
message: string
status_code: number
details?: any
timestamp: string
request_id?: string
}
export interface ValidationError extends ApiError {
validation_errors: {
field: string
message: string
code: string
}[]
}
// Rate Limit Types
export interface RateLimitInfo {
limit: number
remaining: number
reset: number
tier: 'free' | 'basic' | 'premium' | 'enterprise'
}
// File Types
export interface Attachment {
id: string
filename: string
content_type: string
size_bytes: number
url: string
thumbnail_url?: string
uploaded_at: string
}
```
---
## 🚨 Tratamento de Erros
### Padrões de Erro da API
```typescript
// utils/error-handler.ts
export class ApiErrorHandler {
static handle(error: any): never {
if (error.response) {
// Erro da API
const { status, data } = error.response
switch (status) {
case 400:
throw new BadRequestError(data.message || 'Requisição inválida', data)
case 401:
throw new UnauthorizedError(data.message || 'Não autorizado', data)
case 403:
throw new ForbiddenError(data.message || 'Acesso negado', data)
case 404:
throw new NotFoundError(data.message || 'Recurso não encontrado', data)
case 422:
throw new ValidationError(data.message || 'Erro de validação', data.validation_errors)
case 429:
throw new RateLimitError(
data.message || 'Limite de requisições excedido',
data.retry_after
)
case 500:
throw new ServerError(data.message || 'Erro interno do servidor', data)
default:
throw new ApiError(data.message || 'Erro desconhecido', status, data)
}
} else if (error.request) {
// Erro de rede
throw new NetworkError('Erro de conexão com o servidor')
} else {
// Erro desconhecido
throw new UnknownError(error.message || 'Erro desconhecido')
}
}
}
// Classes de erro customizadas
export class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public details?: any
) {
super(message)
this.name = 'ApiError'
}
}
export class BadRequestError extends ApiError {
constructor(message: string, details?: any) {
super(message, 400, details)
this.name = 'BadRequestError'
}
}
export class UnauthorizedError extends ApiError {
constructor(message: string, details?: any) {
super(message, 401, details)
this.name = 'UnauthorizedError'
}
}
export class ValidationError extends ApiError {
constructor(message: string, public validationErrors: any[]) {
super(message, 422, validationErrors)
this.name = 'ValidationError'
}
}
export class RateLimitError extends ApiError {
constructor(message: string, public retryAfter: number) {
super(message, 429, { retry_after: retryAfter })
this.name = 'RateLimitError'
}
}
```
### Hook para Tratamento de Erros
```typescript
// hooks/useApiError.ts
import { useState, useCallback } from 'react'
import { ApiError, ValidationError, RateLimitError } from '@/utils/error-handler'
export function useApiError() {
const [error, setError] = useState<ApiError | null>(null)
const [isLoading, setIsLoading] = useState(false)
const execute = useCallback(async <T>(
apiCall: () => Promise<T>,
options?: {
onSuccess?: (data: T) => void
onError?: (error: ApiError) => void
showToast?: boolean
}
): Promise<T | null> => {
setIsLoading(true)
setError(null)
try {
const result = await apiCall()
options?.onSuccess?.(result)
return result
} catch (err: any) {
const apiError = err instanceof ApiError ? err : new ApiError(
err.message || 'Erro desconhecido',
500
)
setError(apiError)
options?.onError?.(apiError)
if (options?.showToast) {
showErrorToast(apiError)
}
return null
} finally {
setIsLoading(false)
}
}, [])
const clearError = useCallback(() => {
setError(null)
}, [])
return {
error,
isLoading,
execute,
clearError
}
}
// Função auxiliar para mostrar toast
function showErrorToast(error: ApiError) {
let message = error.message
if (error instanceof ValidationError) {
message = error.validationErrors
.map(err => `${err.field}: ${err.message}`)
.join('\n')
} else if (error instanceof RateLimitError) {
message = `${error.message}. Tente novamente em ${error.retryAfter}s`
}
// Implementar toast notification
console.error('Toast:', message)
}
```
---
## ⚡ Rate Limiting
### Headers de Rate Limit
```typescript
interface RateLimitHeaders {
'X-RateLimit-Limit': string // Limite total
'X-RateLimit-Remaining': string // Requisições restantes
'X-RateLimit-Reset': string // Timestamp do reset
'X-RateLimit-Tier': string // Tier atual
}
```
### Implementação de Rate Limit Handler
```typescript
// utils/rate-limit-handler.ts
export class RateLimitHandler {
private static instance: RateLimitHandler
private limitInfo: Map<string, RateLimitInfo> = new Map()
static getInstance(): RateLimitHandler {
if (!RateLimitHandler.instance) {
RateLimitHandler.instance = new RateLimitHandler()
}
return RateLimitHandler.instance
}
updateFromHeaders(endpoint: string, headers: any) {
const limit = parseInt(headers['x-ratelimit-limit'] || '0')
const remaining = parseInt(headers['x-ratelimit-remaining'] || '0')
const reset = parseInt(headers['x-ratelimit-reset'] || '0')
const tier = headers['x-ratelimit-tier'] || 'free'
this.limitInfo.set(endpoint, {
limit,
remaining,
reset,
tier
})
}
getRemainingRequests(endpoint: string): number {
const info = this.limitInfo.get(endpoint)
return info?.remaining ?? -1
}
getResetTime(endpoint: string): Date | null {
const info = this.limitInfo.get(endpoint)
return info ? new Date(info.reset * 1000) : null
}
shouldThrottle(endpoint: string, threshold: number = 10): boolean {
const remaining = this.getRemainingRequests(endpoint)
return remaining !== -1 && remaining < threshold
}
getWaitTime(endpoint: string): number {
const resetTime = this.getResetTime(endpoint)
if (!resetTime) return 0
const now = new Date()
const waitMs = resetTime.getTime() - now.getTime()
return Math.max(0, Math.ceil(waitMs / 1000))
}
}
// Hook para monitorar rate limit
export function useRateLimit(endpoint: string) {
const [limitInfo, setLimitInfo] = useState<RateLimitInfo | null>(null)
const handler = RateLimitHandler.getInstance()
useEffect(() => {
// Atualizar a cada segundo
const interval = setInterval(() => {
const info = handler.limitInfo.get(endpoint)
if (info) {
setLimitInfo({ ...info })
}
}, 1000)
return () => clearInterval(interval)
}, [endpoint])
return {
limit: limitInfo?.limit ?? 0,
remaining: limitInfo?.remaining ?? 0,
reset: limitInfo ? new Date(limitInfo.reset * 1000) : null,
tier: limitInfo?.tier ?? 'free',
shouldThrottle: handler.shouldThrottle(endpoint),
waitTime: handler.getWaitTime(endpoint)
}
}
```
---
## 🎯 Boas Práticas
### 1. Gerenciamento de Estado
```typescript
// store/chat-store.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface ChatStore {
sessions: Map<string, ChatSession>
activeSessionId: string | null
messages: Map<string, ChatMessage[]>
// Actions
setActiveSession: (sessionId: string) => void
addMessage: (sessionId: string, message: ChatMessage) => void
loadSession: (session: ChatSession) => void
clearSession: (sessionId: string) => void
}
export const useChatStore = create<ChatStore>()(
persist(
(set, get) => ({
sessions: new Map(),
activeSessionId: null,
messages: new Map(),
setActiveSession: (sessionId) => set({ activeSessionId: sessionId }),
addMessage: (sessionId, message) => set((state) => {
const messages = state.messages.get(sessionId) || []
state.messages.set(sessionId, [...messages, message])
return { messages: new Map(state.messages) }
}),
loadSession: (session) => set((state) => {
state.sessions.set(session.id, session)
return { sessions: new Map(state.sessions) }
}),
clearSession: (sessionId) => set((state) => {
state.sessions.delete(sessionId)
state.messages.delete(sessionId)
return {
sessions: new Map(state.sessions),
messages: new Map(state.messages)
}
})
}),
{
name: 'cidadao-ai-chat',
partialize: (state) => ({
activeSessionId: state.activeSessionId
})
}
)
)
```
### 2. Otimização de Performance
```typescript
// components/OptimizedChat.tsx
import React, { memo, useMemo, useCallback } from 'react'
import { FixedSizeList as List } from 'react-window'
import AutoSizer from 'react-virtualized-auto-sizer'
// Memoizar componentes de mensagem
const MessageItem = memo(({ message }: { message: ChatMessage }) => {
return (
<div className={`message ${message.role}`}>
<div className="content">{message.content}</div>
<div className="metadata">
{message.timestamp} • {message.agent_used}
</div>
</div>
)
})
// Lista virtualizada para muitas mensagens
export function OptimizedMessageList({ messages }: { messages: ChatMessage[] }) {
const Row = useCallback(({ index, style }: any) => (
<div style={style}>
<MessageItem message={messages[index]} />
</div>
), [messages])
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
itemCount={messages.length}
itemSize={100} // Altura estimada de cada mensagem
width={width}
>
{Row}
</List>
)}
</AutoSizer>
)
}
```
### 3. Cache e Persistência
```typescript
// utils/cache-manager.ts
export class CacheManager {
private static instance: CacheManager
private cache: Map<string, { data: any, expires: number }> = new Map()
static getInstance(): CacheManager {
if (!CacheManager.instance) {
CacheManager.instance = new CacheManager()
}
return CacheManager.instance
}
set(key: string, data: any, ttl: number = 300000) { // 5 minutos padrão
const expires = Date.now() + ttl
this.cache.set(key, { data, expires })
}
get<T>(key: string): T | null {
const item = this.cache.get(key)
if (!item) return null
if (Date.now() > item.expires) {
this.cache.delete(key)
return null
}
return item.data as T
}
invalidate(pattern: string) {
const keys = Array.from(this.cache.keys())
keys.forEach(key => {
if (key.includes(pattern)) {
this.cache.delete(key)
}
})
}
clear() {
this.cache.clear()
}
}
// Hook com cache
export function useCachedApi<T>(
key: string,
fetcher: () => Promise<T>,
options?: {
ttl?: number
refetchOnMount?: boolean
refetchInterval?: number
}
) {
const [data, setData] = useState<T | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const cache = CacheManager.getInstance()
const fetchData = useCallback(async () => {
// Verificar cache primeiro
const cached = cache.get<T>(key)
if (cached) {
setData(cached)
setIsLoading(false)
return cached
}
try {
setIsLoading(true)
const result = await fetcher()
cache.set(key, result, options?.ttl)
setData(result)
return result
} catch (err: any) {
setError(err)
throw err
} finally {
setIsLoading(false)
}
}, [key, fetcher, options?.ttl])
useEffect(() => {
if (options?.refetchOnMount !== false) {
fetchData()
}
if (options?.refetchInterval) {
const interval = setInterval(fetchData, options.refetchInterval)
return () => clearInterval(interval)
}
}, [])
return { data, isLoading, error, refetch: fetchData }
}
```
### 4. Monitoramento e Analytics
```typescript
// utils/analytics.ts
export class Analytics {
static trackEvent(event: string, properties?: any) {
// Implementar tracking
console.log('Track event:', event, properties)
}
static trackApiCall(endpoint: string, duration: number, status: number) {
this.trackEvent('api_call', {
endpoint,
duration,
status,
timestamp: new Date().toISOString()
})
}
static trackError(error: Error, context?: any) {
this.trackEvent('error', {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString()
})
}
static trackPerformance(metric: string, value: number) {
this.trackEvent('performance', {
metric,
value,
timestamp: new Date().toISOString()
})
}
}
// Interceptor para analytics
axios.interceptors.request.use((config) => {
config.metadata = { startTime: Date.now() }
return config
})
axios.interceptors.response.use(
(response) => {
const duration = Date.now() - response.config.metadata.startTime
Analytics.trackApiCall(
response.config.url!,
duration,
response.status
)
return response
},
(error) => {
if (error.response) {
const duration = Date.now() - error.config.metadata.startTime
Analytics.trackApiCall(
error.config.url,
duration,
error.response.status
)
}
Analytics.trackError(error)
return Promise.reject(error)
}
)
```
### 5. Segurança
```typescript
// utils/security.ts
export class Security {
// Sanitizar input do usuário
static sanitizeInput(input: string): string {
return input
.replace(/[<>]/g, '') // Remover tags HTML básicas
.trim()
.slice(0, 5000) // Limitar tamanho
}
// Validar URLs
static isValidUrl(url: string): boolean {
try {
const parsed = new URL(url)
return ['http:', 'https:'].includes(parsed.protocol)
} catch {
return false
}
}
// Storage seguro
static secureStorage = {
setItem(key: string, value: any) {
try {
const encrypted = btoa(JSON.stringify(value))
localStorage.setItem(key, encrypted)
} catch (error) {
console.error('Failed to save to storage:', error)
}
},
getItem<T>(key: string): T | null {
try {
const encrypted = localStorage.getItem(key)
if (!encrypted) return null
return JSON.parse(atob(encrypted)) as T
} catch {
return null
}
},
removeItem(key: string) {
localStorage.removeItem(key)
}
}
}
```
---
## 📚 Recursos Adicionais
### Links Úteis
- **API Documentation**: https://neural-thinker-cidadao-ai-backend.hf.space/docs
- **Redoc**: https://neural-thinker-cidadao-ai-backend.hf.space/redoc
- **Health Check**: https://neural-thinker-cidadao-ai-backend.hf.space/health
### Variáveis de Ambiente Recomendadas
```env
# .env.local
NEXT_PUBLIC_API_URL=https://neural-thinker-cidadao-ai-backend.hf.space
NEXT_PUBLIC_WS_URL=wss://neural-thinker-cidadao-ai-backend.hf.space
NEXT_PUBLIC_APP_NAME=Cidadão.AI
NEXT_PUBLIC_APP_VERSION=1.0.0
NEXT_PUBLIC_ENABLE_ANALYTICS=true
NEXT_PUBLIC_SENTRY_DSN=your-sentry-dsn
```
### Scripts Úteis para package.json
```json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"analyze": "ANALYZE=true next build",
"generate-types": "openapi-typescript https://neural-thinker-cidadao-ai-backend.hf.space/openapi.json --output ./src/types/api-generated.ts"
}
}
```
---
## 🤝 Suporte e Contato
Para dúvidas sobre a integração:
1. Consulte a documentação interativa em `/docs`
2. Verifique os logs de erro retornados pela API
3. Use o endpoint `/health` para verificar status dos serviços
4. Monitore rate limits através dos headers de resposta
Este guia será atualizado conforme novas funcionalidades forem adicionadas ao backend. Mantenha-se atualizado com as versões da API através do endpoint `/api/v1/info`.