1317 lines
28 KiB
Markdown
1317 lines
28 KiB
Markdown
# Documentação Técnica das APIs - OllaHubBackend
|
|
|
|
## Visão Geral
|
|
|
|
O **OllaHubBackend** é uma API REST desenvolvida em Rust (Actix Web) que fornece gerenciamento de modelos Ollama, operações criptográficas, gerenciamento de usuários e comunicação em tempo real via WebSocket.
|
|
|
|
### Base URL
|
|
|
|
- **Desenvolvimento**: `http://127.0.0.1:8080`
|
|
- **Produção**: [A definir]
|
|
|
|
### Versões da API
|
|
|
|
- **V1 (Legado)**: Endpoints implementados diretamente no `main.rs`
|
|
- **V2 (Modular)**: Endpoints organizados em módulos (`src/api/mod.rs`) - **Recomendado**
|
|
- **Usuários V1**: CRUD de usuários em `/api/v1/users`
|
|
|
|
### Swagger UI
|
|
|
|
Documentação interativa disponível em: `http://127.0.0.1:8080/docs`
|
|
|
|
---
|
|
|
|
## Autenticação
|
|
|
|
A API utiliza **JWT (JSON Web Tokens)** para autenticação. Endpoints protegidos requerem o header de autorização.
|
|
|
|
### Como Autenticar
|
|
|
|
```http
|
|
Authorization: Bearer <seu_token_jwt>
|
|
```
|
|
|
|
### Endpoints Protegidos
|
|
|
|
Os seguintes caminhos requerem autenticação JWT:
|
|
- `/api/v1/*` (todos os endpoints de usuários)
|
|
- `/v2/api/crypto/*` (todas as operações de criptografia)
|
|
- `/v2/api/models/*` (operações de modelos)
|
|
|
|
### Endpoints Públicos
|
|
|
|
- `GET /health`
|
|
- `GET /v2/health`
|
|
- `GET /v2/api/ollama/check`
|
|
- `POST /v2/api/ollama/install`
|
|
- `WS /v2/ws` (WebSocket)
|
|
|
|
### Configuração JWT
|
|
|
|
A chave secreta JWT é configurada via variável de ambiente:
|
|
- Variável: `JWT_SECRET`
|
|
- Padrão (dev): `changeme-dev-secret`
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### Sistema / Health
|
|
|
|
#### GET /health
|
|
|
|
Verifica o status básico da API.
|
|
|
|
**Autenticação**: Não requerida
|
|
|
|
**Resposta**:
|
|
```json
|
|
"ok"
|
|
```
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl http://127.0.0.1:8080/health
|
|
```
|
|
|
|
---
|
|
|
|
#### GET /v2/health
|
|
|
|
Verifica o status da API V2.
|
|
|
|
**Autenticação**: Não requerida
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"status": "ok"
|
|
}
|
|
```
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl http://127.0.0.1:8080/v2/health
|
|
```
|
|
|
|
---
|
|
|
|
### Ollama - Instalação e Verificação
|
|
|
|
#### GET /v2/api/ollama/check
|
|
|
|
Verifica se o Ollama está instalado e acessível no sistema.
|
|
|
|
**Autenticação**: Não requerida
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"installed": true,
|
|
"in_path": true,
|
|
"version": "0.1.15",
|
|
"service_running": true,
|
|
"api_status": "reachable",
|
|
"details": {
|
|
"native_version": "ollama version is 0.1.15",
|
|
"api_/api/version": "{\"version\":\"0.1.15\"}",
|
|
"api_/api/tags": "{\"models\":[...]}",
|
|
"security_antivirus_active": "false",
|
|
"security_antivirus_instructions": "..."
|
|
},
|
|
"issues": []
|
|
}
|
|
```
|
|
|
|
**Campos da Resposta**:
|
|
- `installed` (boolean): Se o Ollama está instalado
|
|
- `in_path` (boolean): Se o binário está no PATH
|
|
- `version` (string|null): Versão do Ollama
|
|
- `service_running` (boolean): Se o serviço está rodando
|
|
- `api_status` (string): `"reachable"`, `"unreachable"` ou `"degraded"`
|
|
- `details` (object): Detalhes técnicos da instalação
|
|
- `issues` (array): Lista de problemas encontrados
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl http://127.0.0.1:8080/v2/api/ollama/check
|
|
```
|
|
|
|
---
|
|
|
|
#### POST /v2/api/ollama/install
|
|
|
|
Inicia o processo de instalação automática do Ollama (se não estiver instalado) e garante que o serviço esteja rodando. O progresso é enviado via WebSocket.
|
|
|
|
**Autenticação**: Não requerida
|
|
|
|
**Request Body** (opcional):
|
|
```json
|
|
{
|
|
"method": "auto",
|
|
"platform": "auto"
|
|
}
|
|
```
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"status": "started",
|
|
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
|
|
}
|
|
```
|
|
|
|
**Campos da Resposta**:
|
|
- `status` (string): Status da operação (`"started"`)
|
|
- `operation_id` (string): UUID da operação para rastreamento via WebSocket
|
|
|
|
**Eventos WebSocket**:
|
|
- `Log`: Mensagens de log durante a instalação
|
|
- `Progress`: Progresso da instalação
|
|
- `Done`: Instalação concluída com sucesso
|
|
- `Error`: Erro durante a instalação
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST http://127.0.0.1:8080/v2/api/ollama/install \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"method": "auto", "platform": "auto"}'
|
|
```
|
|
|
|
**Notas**:
|
|
- A instalação é específica por plataforma (Windows, Linux, macOS)
|
|
- O progresso é enviado via WebSocket (`WS /v2/ws`)
|
|
- Em caso de falha, eventos `Error` são enviados via WebSocket
|
|
- Os endpoints V1 (`GET /ollama/install-check` e `POST /ollama/install`) foram removidos
|
|
|
|
---
|
|
|
|
### Modelos (V2)
|
|
|
|
#### GET /v2/api/models
|
|
|
|
Lista todos os modelos disponíveis do arquivo `modelList.json`.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Resposta**:
|
|
```json
|
|
[
|
|
{
|
|
"name": "llama2",
|
|
"description": "Modelo LLaMA 2"
|
|
},
|
|
{
|
|
"name": "mistral",
|
|
"description": "Modelo Mistral"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Sucesso
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `500 Internal Server Error`: Erro ao ler arquivo de modelos
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -H "Authorization: Bearer <token>" \
|
|
http://127.0.0.1:8080/v2/api/models
|
|
```
|
|
|
|
**JavaScript/TypeScript**:
|
|
```typescript
|
|
const response = await fetch('http://127.0.0.1:8080/v2/api/models', {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
const models = await response.json();
|
|
```
|
|
|
|
---
|
|
|
|
#### POST /v2/api/models/install
|
|
|
|
Inicia a instalação (download) de um modelo do Ollama. O progresso é enviado via WebSocket.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"name": "llama2:7b"
|
|
}
|
|
```
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"status": "started",
|
|
"model": "llama2:7b"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `202 Accepted`: Instalação iniciada
|
|
- `400 Bad Request`: Nome do modelo inválido
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `500 Internal Server Error`: Erro ao iniciar instalação
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"llama2:7b"}' \
|
|
http://127.0.0.1:8080/v2/api/models/install
|
|
```
|
|
|
|
**JavaScript/TypeScript**:
|
|
```typescript
|
|
const response = await fetch('http://127.0.0.1:8080/v2/api/models/install', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ name: 'llama2:7b' })
|
|
});
|
|
const result = await response.json();
|
|
```
|
|
|
|
**Notas**:
|
|
- O progresso da instalação é enviado via WebSocket (`/v2/ws`)
|
|
- Conecte-se ao WebSocket antes de iniciar a instalação para receber eventos
|
|
- O formato do nome pode incluir tag: `"modelo:tag"` (ex: `"llama2:7b"`)
|
|
|
|
---
|
|
|
|
### Criptografia (V2)
|
|
|
|
Todas as operações de criptografia usam **AES-256-GCM** com chave de 32 bytes.
|
|
|
|
#### POST /v2/api/crypto/set-key
|
|
|
|
Define a chave de criptografia (deve ser chamado antes de usar encrypt/decrypt).
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"hex_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
}
|
|
```
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"ok": true
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Chave definida com sucesso
|
|
- `400 Bad Request`: Chave inválida (deve ter 64 caracteres hex = 32 bytes)
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `409 Conflict`: Chave já foi definida anteriormente
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"hex_key":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}' \
|
|
http://127.0.0.1:8080/v2/api/crypto/set-key
|
|
```
|
|
|
|
**Notas**:
|
|
- A chave é persistida no disco automaticamente
|
|
- Use a mesma chave no frontend para operações de criptografia
|
|
- A chave deve ter exatamente 64 caracteres hexadecimais (32 bytes)
|
|
|
|
---
|
|
|
|
#### POST /v2/api/crypto/rotate-key
|
|
|
|
Gera uma nova chave de criptografia aleatória e retorna em hexadecimal.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"key": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Chave rotacionada com sucesso
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `500 Internal Server Error`: Erro ao gerar/salvar chave
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
http://127.0.0.1:8080/v2/api/crypto/rotate-key
|
|
```
|
|
|
|
**Notas**:
|
|
- A nova chave substitui a anterior
|
|
- Salve a chave retornada para uso no frontend
|
|
- A chave é automaticamente persistida no disco
|
|
|
|
---
|
|
|
|
#### POST /v2/api/crypto/encrypt
|
|
|
|
Criptografa dados usando AES-256-GCM.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"data": "dados sensíveis para criptografar"
|
|
}
|
|
```
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"ciphertext": "base64_encoded_ciphertext_with_nonce"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Criptografia bem-sucedida
|
|
- `400 Bad Request`: Chave não definida ou dados inválidos
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"data":"minha senha secreta"}' \
|
|
http://127.0.0.1:8080/v2/api/crypto/encrypt
|
|
```
|
|
|
|
**Notas**:
|
|
- O nonce (12 bytes) é incluído automaticamente no início do ciphertext
|
|
- O resultado é retornado em Base64
|
|
- O formato do ciphertext: `[nonce 12 bytes][ciphertext]` (tudo em Base64)
|
|
|
|
---
|
|
|
|
#### POST /v2/api/crypto/decrypt
|
|
|
|
Descriptografa dados usando AES-256-GCM.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"data": "base64_encoded_ciphertext_with_nonce"
|
|
}
|
|
```
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"plaintext": "dados descriptografados"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Descriptografia bem-sucedida
|
|
- `400 Bad Request`: Chave não definida, Base64 inválido ou tag de autenticação inválida
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"data":"ciphertext_base64_aqui"}' \
|
|
http://127.0.0.1:8080/v2/api/crypto/decrypt
|
|
```
|
|
|
|
**Notas**:
|
|
- O nonce é extraído automaticamente dos primeiros 12 bytes
|
|
- O `data` deve ser o mesmo `ciphertext` retornado por `encrypt`
|
|
- A tag de autenticação GCM é verificada automaticamente
|
|
|
|
---
|
|
|
|
### Usuários (V1)
|
|
|
|
#### GET /api/v1/users
|
|
|
|
Lista todos os usuários cadastrados.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Resposta**:
|
|
```json
|
|
[
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"name": "João Silva",
|
|
"email": "[email protected]"
|
|
},
|
|
{
|
|
"id": "660e8400-e29b-41d4-a716-446655440001",
|
|
"name": "Maria Santos",
|
|
"email": "[email protected]"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Sucesso
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -H "Authorization: Bearer <token>" \
|
|
http://127.0.0.1:8080/api/v1/users
|
|
```
|
|
|
|
---
|
|
|
|
#### GET /api/v1/users/{id}
|
|
|
|
Obtém um usuário específico por ID.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Parâmetros de Path**:
|
|
- `id` (string): UUID do usuário
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"name": "João Silva",
|
|
"email": "[email protected]"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Usuário encontrado
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `404 Not Found`: Usuário não encontrado
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -H "Authorization: Bearer <token>" \
|
|
http://127.0.0.1:8080/api/v1/users/550e8400-e29b-41d4-a716-446655440000
|
|
```
|
|
|
|
---
|
|
|
|
#### POST /api/v1/users
|
|
|
|
Cria um novo usuário.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"name": "João Silva",
|
|
"email": "[email protected]"
|
|
}
|
|
```
|
|
|
|
**Validações**:
|
|
- `name`: Mínimo 2 caracteres
|
|
- `email`: Formato de email válido
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"name": "João Silva",
|
|
"email": "[email protected]"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `201 Created`: Usuário criado com sucesso
|
|
- `400 Bad Request`: Erro de validação
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X POST \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"João Silva","email":"[email protected]"}' \
|
|
http://127.0.0.1:8080/api/v1/users
|
|
```
|
|
|
|
---
|
|
|
|
#### PUT /api/v1/users/{id}
|
|
|
|
Atualiza um usuário existente.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Parâmetros de Path**:
|
|
- `id` (string): UUID do usuário
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"name": "João Silva Atualizado",
|
|
"email": "[email protected]"
|
|
}
|
|
```
|
|
|
|
**Validações**:
|
|
- `name`: Mínimo 2 caracteres (opcional)
|
|
- `email`: Formato de email válido (opcional)
|
|
|
|
**Resposta**:
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"name": "João Silva Atualizado",
|
|
"email": "[email protected]"
|
|
}
|
|
```
|
|
|
|
**Códigos de Status**:
|
|
- `200 OK`: Usuário atualizado
|
|
- `400 Bad Request`: Erro de validação
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `404 Not Found`: Usuário não encontrado
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X PUT \
|
|
-H "Authorization: Bearer <token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"João Silva Atualizado","email":"[email protected]"}' \
|
|
http://127.0.0.1:8080/api/v1/users/550e8400-e29b-41d4-a716-446655440000
|
|
```
|
|
|
|
---
|
|
|
|
#### DELETE /api/v1/users/{id}
|
|
|
|
Remove um usuário.
|
|
|
|
**Autenticação**: Requerida (JWT)
|
|
|
|
**Parâmetros de Path**:
|
|
- `id` (string): UUID do usuário
|
|
|
|
**Resposta**: Sem corpo (204 No Content)
|
|
|
|
**Códigos de Status**:
|
|
- `204 No Content`: Usuário removido
|
|
- `401 Unauthorized`: Token JWT inválido ou ausente
|
|
- `404 Not Found`: Usuário não encontrado
|
|
|
|
**Exemplo**:
|
|
```bash
|
|
curl -X DELETE \
|
|
-H "Authorization: Bearer <token>" \
|
|
http://127.0.0.1:8080/api/v1/users/550e8400-e29b-41d4-a716-446655440000
|
|
```
|
|
|
|
---
|
|
|
|
### WebSocket (V2)
|
|
|
|
#### WS /v2/ws
|
|
|
|
Endpoint WebSocket para receber eventos em tempo real, especialmente durante a instalação de modelos.
|
|
|
|
**Autenticação**: Não requerida (mas pode ser implementada)
|
|
|
|
**Conexão**:
|
|
```
|
|
ws://127.0.0.1:8080/v2/ws
|
|
```
|
|
|
|
**Tipos de Eventos**:
|
|
|
|
1. **Log**: Mensagens de log durante operações
|
|
```json
|
|
{
|
|
"kind": "Log",
|
|
"message": "Downloading model llama2:7b..."
|
|
}
|
|
```
|
|
|
|
2. **Progress**: Progresso de operações (ex: download de modelo)
|
|
```json
|
|
{
|
|
"kind": "Progress",
|
|
"message": "{\"status\":\"downloading\",\"completed\":50,\"total\":100}"
|
|
}
|
|
```
|
|
|
|
3. **Done**: Operação concluída
|
|
```json
|
|
{
|
|
"kind": "Done",
|
|
"message": "Download do modelo 'llama2:7b' concluído"
|
|
}
|
|
```
|
|
|
|
4. **Error**: Erro durante operação
|
|
```json
|
|
{
|
|
"kind": "Error",
|
|
"message": "Falha ao conectar com API do Ollama"
|
|
}
|
|
```
|
|
|
|
**Exemplo JavaScript**:
|
|
```javascript
|
|
const ws = new WebSocket('ws://127.0.0.1:8080/v2/ws');
|
|
|
|
ws.onopen = () => {
|
|
console.log('WebSocket conectado');
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
console.log('Evento recebido:', data);
|
|
|
|
switch(data.kind) {
|
|
case 'Log':
|
|
console.log('Log:', data.message);
|
|
break;
|
|
case 'Progress':
|
|
console.log('Progresso:', data.message);
|
|
break;
|
|
case 'Done':
|
|
console.log('Concluído:', data.message);
|
|
break;
|
|
case 'Error':
|
|
console.error('Erro:', data.message);
|
|
break;
|
|
}
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error('Erro WebSocket:', error);
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log('WebSocket desconectado');
|
|
};
|
|
```
|
|
|
|
**Exemplo TypeScript**:
|
|
```typescript
|
|
enum WsEventKind {
|
|
Log = 'Log',
|
|
Progress = 'Progress',
|
|
Done = 'Done',
|
|
Error = 'Error'
|
|
}
|
|
|
|
interface WsEvent {
|
|
kind: WsEventKind;
|
|
message: string;
|
|
}
|
|
|
|
const ws = new WebSocket('ws://127.0.0.1:8080/v2/ws');
|
|
|
|
ws.onmessage = (event: MessageEvent) => {
|
|
const data: WsEvent = JSON.parse(event.data);
|
|
// Processar evento
|
|
};
|
|
```
|
|
|
|
**Fluxo de Instalação de Modelo**:
|
|
|
|
1. Conecte-se ao WebSocket
|
|
2. Chame `POST /v2/api/models/install` com o nome do modelo
|
|
3. Receba eventos via WebSocket:
|
|
- `Log`: Mensagens de progresso
|
|
- `Progress`: Atualizações de progresso (se disponível)
|
|
- `Done`: Instalação concluída
|
|
- `Error`: Erro durante instalação
|
|
|
|
**Notas**:
|
|
- O WebSocket mantém a conexão aberta para receber múltiplos eventos
|
|
|
|
---
|
|
|
|
### WebSocket Chat (`/v2/ws/chat`)
|
|
|
|
Endpoint WebSocket dedicado para chat com modelos Ollama, oferecendo streaming em tempo real das respostas.
|
|
|
|
#### Conectar
|
|
|
|
```javascript
|
|
const ws = new WebSocket('ws://127.0.0.1:8080/v2/ws/chat');
|
|
|
|
ws.onopen = () => {
|
|
console.log('Chat WebSocket conectado');
|
|
|
|
// Enviar mensagem de chat
|
|
const chatRequest = {
|
|
model: "llama2",
|
|
messages: [
|
|
{ role: "user", content: "Olá, como você está?" }
|
|
],
|
|
stream: true,
|
|
options: {
|
|
temperature: 0.7,
|
|
top_p: 0.9
|
|
}
|
|
};
|
|
|
|
ws.send(JSON.stringify(chatRequest));
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
const response = JSON.parse(event.data);
|
|
|
|
if (response.done) {
|
|
console.log('Chat finalizado');
|
|
return;
|
|
}
|
|
|
|
if (response.message) {
|
|
// Acumular conteúdo da mensagem
|
|
console.log('Chunk recebido:', response.message.content);
|
|
}
|
|
|
|
if (response.error) {
|
|
console.error('Erro no chat:', response.error);
|
|
}
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error('Erro WebSocket chat:', error);
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log('Chat WebSocket desconectado');
|
|
};
|
|
```
|
|
|
|
#### Formato da Requisição
|
|
|
|
```json
|
|
{
|
|
"model": "llama2",
|
|
"messages": [
|
|
{ "role": "user", "content": "Sua mensagem aqui" },
|
|
{ "role": "assistant", "content": "Resposta anterior" }
|
|
],
|
|
"stream": true,
|
|
"options": {
|
|
"temperature": 0.7,
|
|
"top_p": 0.9,
|
|
"top_k": 40
|
|
}
|
|
}
|
|
```
|
|
|
|
**Campos**:
|
|
- `model` (string, obrigatório): Nome do modelo a ser usado
|
|
- `messages` (array, obrigatório): Array de mensagens no formato `{ role: "user"|"assistant", content: string }`
|
|
- `stream` (boolean, opcional): Sempre `true` para streaming (padrão: `true`)
|
|
- `options` (object, opcional): Opções do modelo (temperature, top_p, top_k, etc.)
|
|
|
|
#### Formato da Resposta (Streaming)
|
|
|
|
Cada chunk recebido é um objeto JSON:
|
|
|
|
```json
|
|
{
|
|
"model": "llama2",
|
|
"created_at": "2024-01-01T12:00:00Z",
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": "Olá! Estou bem, obrigado por perguntar."
|
|
},
|
|
"done": false
|
|
}
|
|
```
|
|
|
|
**Último chunk** (quando `done: true`):
|
|
```json
|
|
{
|
|
"model": "llama2",
|
|
"created_at": "2024-01-01T12:00:05Z",
|
|
"message": null,
|
|
"done": true,
|
|
"total_duration": 5000000000,
|
|
"load_duration": 100000000,
|
|
"prompt_eval_count": 10,
|
|
"prompt_eval_duration": 200000000,
|
|
"eval_count": 50,
|
|
"eval_duration": 4700000000
|
|
}
|
|
```
|
|
|
|
#### Características
|
|
|
|
- **Streaming em tempo real**: Respostas são enviadas chunk por chunk conforme geradas
|
|
- **Baixa latência**: WebSocket mantém conexão persistente
|
|
- **Heartbeat**: Ping/pong automático a cada 5 segundos
|
|
- **Timeout**: Desconexão automática após 30 segundos de inatividade
|
|
- **Múltiplas conversas**: Uma conexão pode enviar múltiplas requisições de chat
|
|
|
|
#### Exemplo Completo: Conversa com Histórico
|
|
|
|
```javascript
|
|
const ws = new WebSocket('ws://127.0.0.1:8080/v2/ws/chat');
|
|
let conversationHistory = [];
|
|
|
|
ws.onopen = () => {
|
|
sendMessage("Olá!");
|
|
};
|
|
|
|
function sendMessage(userMessage) {
|
|
// Adicionar mensagem do usuário ao histórico
|
|
conversationHistory.push({ role: "user", content: userMessage });
|
|
|
|
const request = {
|
|
model: "llama2",
|
|
messages: conversationHistory,
|
|
stream: true
|
|
};
|
|
|
|
ws.send(JSON.stringify(request));
|
|
}
|
|
|
|
ws.onmessage = (event) => {
|
|
const response = JSON.parse(event.data);
|
|
|
|
if (response.done) {
|
|
// Adicionar resposta completa ao histórico
|
|
if (response.message) {
|
|
conversationHistory.push(response.message);
|
|
}
|
|
console.log('Conversa atualizada:', conversationHistory);
|
|
return;
|
|
}
|
|
|
|
// Acumular chunks da resposta
|
|
if (response.message && response.message.content) {
|
|
process.stdout.write(response.message.content);
|
|
}
|
|
};
|
|
|
|
// Enviar próxima mensagem após resposta completa
|
|
ws.onmessage = (event) => {
|
|
const response = JSON.parse(event.data);
|
|
|
|
if (response.done) {
|
|
if (response.message) {
|
|
conversationHistory.push(response.message);
|
|
}
|
|
// Próxima mensagem do usuário
|
|
sendMessage("Conte-me uma piada");
|
|
} else if (response.message) {
|
|
process.stdout.write(response.message.content);
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Tratamento de Erros
|
|
|
|
```javascript
|
|
ws.onmessage = (event) => {
|
|
const response = JSON.parse(event.data);
|
|
|
|
if (response.error) {
|
|
console.error('Erro do Ollama:', response.error);
|
|
// Erro pode ocorrer em qualquer chunk
|
|
}
|
|
|
|
if (response.done && response.error) {
|
|
// Erro final - conexão pode ser fechada
|
|
ws.close();
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Notas Importantes
|
|
|
|
- O endpoint requer autenticação JWT (mesmo token usado em outras APIs)
|
|
- O WebSocket mantém a conexão aberta para múltiplas requisições
|
|
- Cada requisição deve incluir o histórico completo da conversa
|
|
- Respostas são enviadas em formato JSON lines (uma linha por chunk)
|
|
- Use `done: true` para saber quando a resposta está completa
|
|
- Eventos são broadcast para todos os clientes conectados
|
|
- Ping/Pong são gerenciados automaticamente
|
|
|
|
---
|
|
|
|
## Modelos de Dados
|
|
|
|
### User
|
|
|
|
```typescript
|
|
interface User {
|
|
id: string; // UUID
|
|
name: string; // Mínimo 2 caracteres
|
|
email: string; // Email válido
|
|
}
|
|
```
|
|
|
|
### CreateUserRequest
|
|
|
|
```typescript
|
|
interface CreateUserRequest {
|
|
name: string; // Mínimo 2 caracteres
|
|
email: string; // Email válido
|
|
}
|
|
```
|
|
|
|
### UpdateUserRequest
|
|
|
|
```typescript
|
|
interface UpdateUserRequest {
|
|
name?: string; // Mínimo 2 caracteres (opcional)
|
|
email?: string; // Email válido (opcional)
|
|
}
|
|
```
|
|
|
|
### ModelInfo
|
|
|
|
```typescript
|
|
interface ModelInfo {
|
|
name: string;
|
|
description?: string;
|
|
}
|
|
```
|
|
|
|
### InstallReq
|
|
|
|
```typescript
|
|
interface InstallReq {
|
|
name: string; // Nome do modelo (ex: "llama2:7b")
|
|
}
|
|
```
|
|
|
|
### SetKeyReq
|
|
|
|
```typescript
|
|
interface SetKeyReq {
|
|
hex_key: string; // 64 caracteres hex (32 bytes)
|
|
}
|
|
```
|
|
|
|
### CryptReq
|
|
|
|
```typescript
|
|
interface CryptReq {
|
|
data: string; // Dados para criptografar/descriptografar
|
|
}
|
|
```
|
|
|
|
### ApiErrorBody
|
|
|
|
```typescript
|
|
interface ApiErrorBody {
|
|
error: string; // Mensagem de erro
|
|
}
|
|
```
|
|
|
|
### WsEvent
|
|
|
|
```typescript
|
|
enum WsEventKind {
|
|
Log = 'Log',
|
|
Progress = 'Progress',
|
|
Done = 'Done',
|
|
Error = 'Error'
|
|
}
|
|
|
|
interface WsEvent {
|
|
kind: WsEventKind;
|
|
message: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Tratamento de Erros
|
|
|
|
### Códigos de Status HTTP
|
|
|
|
- `200 OK`: Requisição bem-sucedida
|
|
- `201 Created`: Recurso criado com sucesso
|
|
- `202 Accepted`: Requisição aceita para processamento assíncrono
|
|
- `204 No Content`: Sucesso sem conteúdo de retorno
|
|
- `400 Bad Request`: Erro na requisição (validação, formato)
|
|
- `401 Unauthorized`: Token JWT ausente ou inválido
|
|
- `404 Not Found`: Recurso não encontrado
|
|
- `409 Conflict`: Conflito (ex: chave já definida)
|
|
- `500 Internal Server Error`: Erro interno do servidor
|
|
|
|
### Formato de Erro
|
|
|
|
Todos os erros retornam o seguinte formato:
|
|
|
|
```json
|
|
{
|
|
"error": "Mensagem de erro descritiva"
|
|
}
|
|
```
|
|
|
|
### Exemplos de Erros
|
|
|
|
**400 Bad Request**:
|
|
```json
|
|
{
|
|
"error": "validação: email: deve ser um email válido"
|
|
}
|
|
```
|
|
|
|
**401 Unauthorized**:
|
|
```json
|
|
{
|
|
"error": "Token JWT inválido ou ausente"
|
|
}
|
|
```
|
|
|
|
**404 Not Found**:
|
|
```json
|
|
{
|
|
"error": "Usuário não encontrado"
|
|
}
|
|
```
|
|
|
|
**500 Internal Server Error**:
|
|
```json
|
|
{
|
|
"error": "Falha ao ler arquivo de modelos: No such file or directory"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Variáveis de Ambiente
|
|
|
|
### OLLAMA_BASE_URL
|
|
|
|
- **Descrição**: URL base da API do Ollama
|
|
- **Padrão**: `http://localhost:11434`
|
|
- **Uso**: Usado para comunicação com o Ollama
|
|
|
|
### MODEL_LIST_PATH
|
|
|
|
- **Descrição**: Caminho para o arquivo JSON com lista de modelos
|
|
- **Padrão**: `modelList.json`
|
|
- **Uso**: Arquivo lido para listar modelos disponíveis
|
|
|
|
### JWT_SECRET
|
|
|
|
- **Descrição**: Chave secreta para assinatura de tokens JWT
|
|
- **Padrão**: `changeme-dev-secret`
|
|
- **Uso**: Validação de tokens JWT
|
|
- **Importante**:
|
|
- Deve ser o **mesmo valor** usado no frontend para gerar tokens
|
|
- Altere em produção!
|
|
- Pode ser configurado via variável de ambiente ou arquivo `.env`
|
|
|
|
**Configuração via `.env`** (recomendado):
|
|
```env
|
|
JWT_SECRET=5741f9da3be6007674a5e10edab7b124
|
|
```
|
|
|
|
**Configuração via variável de ambiente**:
|
|
```bash
|
|
# Windows (PowerShell)
|
|
$env:JWT_SECRET="5741f9da3be6007674a5e10edab7b124"
|
|
|
|
# Linux/Mac
|
|
export JWT_SECRET="5741f9da3be6007674a5e10edab7b124"
|
|
```
|
|
|
|
### SKIP_ANTIVIRUS_WARNINGS
|
|
|
|
- **Descrição**: Suprime avisos de verificação de antivírus durante inicialização
|
|
- **Padrão**: `false`
|
|
- **Valores**: `true` ou `false`
|
|
- **Uso**: Define como `true` para desabilitar avisos informativos sobre antivírus/segurança
|
|
- **Exemplo**: `SKIP_ANTIVIRUS_WARNINGS=true cargo run`
|
|
|
|
---
|
|
|
|
## Exemplos de Fluxos Completos
|
|
|
|
### Fluxo 1: Instalar um Modelo com Acompanhamento via WebSocket
|
|
|
|
```javascript
|
|
// 1. Conectar ao WebSocket
|
|
const ws = new WebSocket('ws://127.0.0.1:8080/v2/ws');
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.kind === 'Done') {
|
|
console.log('Modelo instalado!');
|
|
}
|
|
};
|
|
|
|
// 2. Aguardar conexão
|
|
ws.onopen = async () => {
|
|
// 3. Iniciar instalação
|
|
const response = await fetch('http://127.0.0.1:8080/v2/api/models/install', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ name: 'llama2:7b' })
|
|
});
|
|
|
|
const result = await response.json();
|
|
console.log('Instalação iniciada:', result);
|
|
};
|
|
```
|
|
|
|
### Fluxo 2: Configurar Criptografia e Criptografar Dados
|
|
|
|
```javascript
|
|
// 1. Definir chave (ou rotacionar)
|
|
const rotateResponse = await fetch('http://127.0.0.1:8080/v2/api/crypto/rotate-key', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
const { key } = await rotateResponse.json();
|
|
console.log('Nova chave:', key);
|
|
|
|
// 2. Criptografar dados
|
|
const encryptResponse = await fetch('http://127.0.0.1:8080/v2/api/crypto/encrypt', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ data: 'dados sensíveis' })
|
|
});
|
|
|
|
const { ciphertext } = await encryptResponse.json();
|
|
console.log('Dados criptografados:', ciphertext);
|
|
|
|
// 3. Descriptografar
|
|
const decryptResponse = await fetch('http://127.0.0.1:8080/v2/api/crypto/decrypt', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ data: ciphertext })
|
|
});
|
|
|
|
const { plaintext } = await decryptResponse.json();
|
|
console.log('Dados descriptografados:', plaintext);
|
|
```
|
|
|
|
### Fluxo 3: CRUD Completo de Usuários
|
|
|
|
```javascript
|
|
const baseUrl = 'http://127.0.0.1:8080/api/v1/users';
|
|
const headers = {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
// 1. Criar usuário
|
|
const createResponse = await fetch(baseUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
name: 'João Silva',
|
|
email: '[email protected]'
|
|
})
|
|
});
|
|
const newUser = await createResponse.json();
|
|
console.log('Usuário criado:', newUser);
|
|
|
|
// 2. Listar usuários
|
|
const listResponse = await fetch(baseUrl, { headers });
|
|
const users = await listResponse.json();
|
|
console.log('Usuários:', users);
|
|
|
|
// 3. Obter usuário específico
|
|
const getUserResponse = await fetch(`${baseUrl}/${newUser.id}`, { headers });
|
|
const user = await getUserResponse.json();
|
|
console.log('Usuário:', user);
|
|
|
|
// 4. Atualizar usuário
|
|
const updateResponse = await fetch(`${baseUrl}/${newUser.id}`, {
|
|
method: 'PUT',
|
|
headers,
|
|
body: JSON.stringify({
|
|
name: 'João Silva Atualizado',
|
|
email: '[email protected]'
|
|
})
|
|
});
|
|
const updatedUser = await updateResponse.json();
|
|
console.log('Usuário atualizado:', updatedUser);
|
|
|
|
// 5. Deletar usuário
|
|
await fetch(`${baseUrl}/${newUser.id}`, {
|
|
method: 'DELETE',
|
|
headers
|
|
});
|
|
console.log('Usuário deletado');
|
|
```
|
|
|
|
---
|
|
|
|
## Notas Importantes
|
|
|
|
### Segurança
|
|
|
|
1. **JWT Secret**: Sempre altere o `JWT_SECRET` em produção
|
|
2. **HTTPS**: Use HTTPS em produção para proteger tokens e dados
|
|
3. **Chave de Criptografia**: Mantenha a chave de criptografia segura e nunca a exponha
|
|
4. **Validação**: Todos os inputs são validados no backend
|
|
|
|
### Performance
|
|
|
|
1. **WebSocket**: Use WebSocket para operações longas (instalação de modelos)
|
|
2. **Assíncrono**: Instalação de modelos é assíncrona (202 Accepted)
|
|
3. **Cache**: Considere cachear lista de modelos no frontend
|
|
|
|
### Compatibilidade
|
|
|
|
1. **V1 vs V2**: Prefira endpoints V2 quando disponíveis
|
|
2. **Swagger**: Use `/docs` para documentação interativa
|
|
3. **Versões**: Endpoints V1 podem ser descontinuados no futuro
|
|
|
|
### Limitações
|
|
|
|
1. **Armazenamento de Usuários**: Atualmente em memória (perdido ao reiniciar)
|
|
2. **Chave de Criptografia**: Uma chave por instância do servidor
|
|
3. **WebSocket**: Broadcast para todos os clientes (não filtrado por operação)
|
|
|
|
---
|
|
|
|
## Suporte e Recursos
|
|
|
|
- **Swagger UI**: `http://127.0.0.1:8080/docs`
|
|
- **Health Check**: `http://127.0.0.1:8080/health`
|
|
- **Código Fonte**: Ver repositório do projeto
|
|
|
|
---
|
|
|
|
**Última Atualização**: 2024
|
|
|