feat: initialize backend structure with core modules and services
- Add base project structure with Cargo.toml and .env - Implement core modules: models, services, repositories, controllers - Set up logging, error handling, and middleware - Configure routes and API documentation - Add crypto and websocket functionality - Include basic user management system
This commit is contained in:
1 parent
23ba2c34ad
commit
f82191dd83
30 files changed
+5957
-1
No files matched your search
@@ -0,0 +1,44 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
## Prompt Versão 2 (Conciso)
|
||||
|
||||
```
|
||||
## ESPECIALISTA EM RUST PARA INICIANTES - MODO DIDÁTICO DIRETO
|
||||
|
||||
### Configuração Principal
|
||||
Você é um instrutor de Rust especializado em ensino para iniciantes absolutos. Sua comunicação segue estas regras rigorosamente:
|
||||
|
||||
**Concisão com Qualidade:**
|
||||
- Máximo 2-3 frases por explicação, sem floreios
|
||||
- Uma idea por parágrafo
|
||||
- Omitir redundâncias
|
||||
|
||||
**Explicação de Erros:**
|
||||
- Estrutura: [O que aconteceu] → [Por quê] → [Como corrigir]
|
||||
- Sempre com analogia/comparação simples
|
||||
- Nunca exceda 1 parágrafo por erro
|
||||
|
||||
**Sequência de Raciocínio:**
|
||||
1. Identifique qual conceito está sendo abordado
|
||||
2. Relate com conhecimento anterior do usuário
|
||||
3. Forneça exemplo funcional mínimo
|
||||
4. Mostre o erro comum e correção
|
||||
|
||||
### Comportamento na Interação
|
||||
- Quando o usuário pede uma explicação: context breve → conceito → código mínimo funcionando
|
||||
- Quando ocorre erro: analise o tipo de erro → explique por que ocorreu → solução em 1-2 linhas de correção
|
||||
- Se pedir sobre ownership/borrow checker: use metáfora de "empréstimo de livro" ou similar
|
||||
|
||||
### Exemplos de Tom Esperado
|
||||
|
||||
**Ruim:** "A propriedade em Rust é um conceito fundamental que permite..."
|
||||
**Bom:** "Propriedade = só um dono por vez (como uma chave única)."
|
||||
|
||||
**Ruim:** "Este erro ocorre porque a função tenta acessar a variável..."
|
||||
**Bom:** "Erro: tentou usar `x` após move. Por quê? `x` foi transferido. Solução: use referência `&x` ou clone se precisar cópia."
|
||||
|
||||
### Foco de Aprendizado
|
||||
Prioridades: Variables → Data Types → Functions → Control Flow → Ownership/Borrowing → Enums/Structs → Error Handling
|
||||
|
||||
Adapte profundidade conforme necessário, mas SEMPRE mantenha a brevidade.
|
||||
+5
-1
@@ -14,4 +14,8 @@ target/
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
#.idea/
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
Generated
+2745
File diff suppressed because it is too large.
Load diff
+26
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "OllaHubBackend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13.5"
|
||||
actix-rt = "2.11.0"
|
||||
actix-web = "4.11.0"
|
||||
actix-web-actors = "4.3.0"
|
||||
actix-web-lab = "0.24.3"
|
||||
awc = "3.8.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sysinfo = "0.30"
|
||||
tokio = { version = "1", features = ["sync","rt","macros"] }
|
||||
aes-gcm = "0.10"
|
||||
rand = "0.8"
|
||||
base64 = "0.21"
|
||||
dirs = "5.0"
|
||||
uuid = { version = "1", features = ["v4","serde"] }
|
||||
hex = "0.4"
|
||||
utoipa = { version = "4", features = ["actix_extras"] }
|
||||
utoipa-swagger-ui = { version = "4", features = ["actix-web"] }
|
||||
validator = { version = "0.16", features = ["derive"] }
|
||||
jsonwebtoken = "9"
|
||||
@@ -0,0 +1,88 @@
|
||||
# Ollama – Verificação, Instalação e Inicialização
|
||||
|
||||
Você quer garantir que o Ollama esteja instalado e rodando sem drama. Este backend expõe dois endpoints para isso: um que verifica o estado atual e outro que instala e inicia o serviço quando necessário.
|
||||
|
||||
Uma linha direta: porta local é `11434`. Se a API responde, tá funcionando.
|
||||
|
||||
## O que os endpoints fazem
|
||||
|
||||
- `GET /ollama/install-check`
|
||||
- Checa se o binário está no `PATH`
|
||||
- Lê a versão (`ollama -v`) quando disponível
|
||||
- Verifica se o serviço está ativo (Linux via `systemctl`; Windows/macOS por processo/API)
|
||||
- Testa a API local: `/api/version` e `/api/tags`
|
||||
|
||||
- `POST /ollama/install`
|
||||
- Instala o Ollama conforme seu sistema operacional
|
||||
- Configura serviço (quando aplicável)
|
||||
- Inicia e valida a API com polling
|
||||
- Faz rollback básico se algo falhar (para serviço no Linux)
|
||||
|
||||
## Requisitos mínimos
|
||||
|
||||
- Porta `11434` livre (localhost)
|
||||
- Rede para baixar pacotes (instalação)
|
||||
- Permissões adequadas:
|
||||
- Linux: `systemctl` (para `enable/start`) pode exigir privilégios
|
||||
- Windows: instalador pode abrir GUI; usamos fallback por `.zip` quando possível
|
||||
- macOS: se tiver Homebrew, instalamos; caso contrário, baixe manualmente
|
||||
|
||||
### GPU (opcional)
|
||||
- NVIDIA: bibliotecas CUDA adequadas do pacote do Ollama
|
||||
- AMD: ROCm (Windows requer pacote adicional)
|
||||
- CPU-only funciona, só é mais lento
|
||||
|
||||
## Detalhes por sistema
|
||||
|
||||
### Linux
|
||||
- Instalação oficial via script:
|
||||
- `curl -fsSL https://ollama.com/install.sh | sh`
|
||||
- Serviço: `systemctl enable ollama` e `systemctl start ollama`
|
||||
- Verificação de serviço: `systemctl is-active ollama`
|
||||
- API local: `http://localhost:11434`
|
||||
- Ajustes de segurança (se necessário): SELinux/AppArmor
|
||||
|
||||
### Windows
|
||||
- Preferência: `OllamaSetup.exe` (instala no seu perfil)
|
||||
- Fallback: pacote `.zip` standalone para `LOCALAPPDATA\Programs\Ollama`
|
||||
- Início do serviço: `ollama serve` (se não rodando)
|
||||
- API local: `http://localhost:11434`
|
||||
- Variáveis de ambiente: defina `OLLAMA_HOST`, `OLLAMA_MODELS` em Configurações do Windows
|
||||
- Login item: desative em `Task Manager > Startup apps` se não quiser iniciar junto com o sistema
|
||||
|
||||
### macOS
|
||||
- Se tiver Homebrew: `brew install ollama`
|
||||
- Senão: baixe o app em https://ollama.com/download/
|
||||
- Início do serviço: `ollama serve` se o CLI estiver disponível
|
||||
- API local: `http://localhost:11434`
|
||||
|
||||
## Erros comuns e soluções
|
||||
|
||||
- Sem permissão para `systemctl`
|
||||
- Solução: rode com privilégios ou instale para usuário e inicie com `ollama serve`
|
||||
|
||||
- Download bloqueado por proxy/antivírus
|
||||
- Solução: autorize o binário/endpoint, tente novamente; em último caso, baixe manualmente
|
||||
|
||||
- API inacessível após instalação
|
||||
- Solução: verifique logs (`systemctl status ollama` no Linux, `%LOCALAPPDATA%\Ollama\server.log` no Windows), confirme porta 11434 livre
|
||||
|
||||
## Exemplos rápidos
|
||||
|
||||
Verificar instalação:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/ollama/install-check
|
||||
```
|
||||
|
||||
Instalar e iniciar:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ollama/install
|
||||
```
|
||||
|
||||
## Observações finais
|
||||
|
||||
- MacOS/Windows costumam registrar Ollama como login item. Desative se preferir.
|
||||
- Linux: se não usar `systemd`, inicie com `ollama serve` e considere ferramentas alternativas de serviço.
|
||||
- Se algo quebrar, os logs do endpoint vão apontar a etapa e o erro. Isso ajuda a corrigir rápido.
|
||||
+967
@@ -0,0 +1,967 @@
|
||||
{
|
||||
"metadata": {
|
||||
"source": "ollama.com/library + buscas: uncensored, abliterated, code, reasoning",
|
||||
"collection_date": "2025-10-29",
|
||||
"total_models": 270,
|
||||
"note": "Integração de modelos oficiais + comunitários (uncensored/abliterated variants). Modelos de reasoning incluem variantes com foco em raciocínio (ex: CoT, logical deduction). Duplicatas removidas; métricas atualizadas onde possível. API para lista remota ainda limitada (ver GitHub #8241)."
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"name": "gpt-oss",
|
||||
"description": "OpenAI's open-weight models designed for powerful reasoning, agentic tasks, and versatile developer use cases",
|
||||
"capabilities": ["tools", "thinking", "cloud"],
|
||||
"parameter_sizes": ["20b", "120b"],
|
||||
"pulls": "3.8M",
|
||||
"tags_count": 5,
|
||||
"last_updated": "2 weeks ago",
|
||||
"url": "https://ollama.com/library/gpt-oss"
|
||||
},
|
||||
{
|
||||
"name": "qwen3-vl",
|
||||
"description": "The most powerful vision-language model in the Qwen model family to date",
|
||||
"capabilities": ["vision", "cloud"],
|
||||
"parameter_sizes": ["2b", "4b", "8b", "30b", "32b"],
|
||||
"pulls": "22.8K",
|
||||
"tags_count": 49,
|
||||
"last_updated": "13 minutes ago",
|
||||
"url": "https://ollama.com/library/qwen3-vl"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-r1",
|
||||
"description": "DeepSeek-R1 is a family of open reasoning models with performance approaching that of leading models, such as O3 and Gemini 2.5 Pro",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["1.5b", "7b", "8b", "14b", "32b", "70b", "671b"],
|
||||
"pulls": "68.4M",
|
||||
"tags_count": 35,
|
||||
"last_updated": "3 months ago",
|
||||
"url": "https://ollama.com/library/deepseek-r1"
|
||||
},
|
||||
{
|
||||
"name": "qwen3-coder",
|
||||
"description": "Alibaba's performant long context models for agentic and coding tasks",
|
||||
"capabilities": ["tools", "cloud"],
|
||||
"parameter_sizes": ["30b", "480b"],
|
||||
"pulls": "575.7K",
|
||||
"tags_count": 10,
|
||||
"last_updated": "1 month ago",
|
||||
"url": "https://ollama.com/library/qwen3-coder"
|
||||
},
|
||||
{
|
||||
"name": "gemma3",
|
||||
"description": "The current, most capable model that runs on a single GPU",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["270m", "1b", "4b", "12b", "27b"],
|
||||
"pulls": "23.1M",
|
||||
"tags_count": 26,
|
||||
"last_updated": "2 months ago",
|
||||
"url": "https://ollama.com/library/gemma3"
|
||||
},
|
||||
{
|
||||
"name": "glm-4.6",
|
||||
"description": "Advanced agentic, reasoning and coding capabilities",
|
||||
"capabilities": ["cloud"],
|
||||
"parameter_sizes": [],
|
||||
"pulls": "11.8K",
|
||||
"tags_count": 1,
|
||||
"last_updated": "2 weeks ago",
|
||||
"url": "https://ollama.com/library/glm-4.6"
|
||||
},
|
||||
{
|
||||
"name": "embeddinggemma",
|
||||
"description": "EmbeddingGemma is a 300M parameter embedding model from Google",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": ["300m"],
|
||||
"pulls": "111.2K",
|
||||
"tags_count": 5,
|
||||
"last_updated": "1 month ago",
|
||||
"url": "https://ollama.com/library/embeddinggemma"
|
||||
},
|
||||
{
|
||||
"name": "qwen3",
|
||||
"description": "Qwen3 is the latest generation of large language models in Qwen series, offering a comprehensive suite of dense and mixture-of-experts (MoE) models",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["0.6b", "1.7b", "4b", "8b", "14b", "30b", "32b", "235b"],
|
||||
"pulls": "11.7M",
|
||||
"tags_count": 58,
|
||||
"last_updated": "2 weeks ago",
|
||||
"url": "https://ollama.com/library/qwen3"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-v3.1",
|
||||
"description": "DeepSeek-V3.1-Terminus is a hybrid model that supports both thinking mode and non-thinking mode",
|
||||
"capabilities": ["tools", "thinking", "cloud"],
|
||||
"parameter_sizes": ["671b"],
|
||||
"pulls": "120.6K",
|
||||
"tags_count": 8,
|
||||
"last_updated": "1 month ago",
|
||||
"url": "https://ollama.com/library/deepseek-v3.1"
|
||||
},
|
||||
{
|
||||
"name": "llama3.1",
|
||||
"description": "Llama 3.1 is a new state-of-the-art model from Meta available in 8B, 70B and 405B parameter sizes",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["8b", "70b", "405b"],
|
||||
"pulls": "105.1M",
|
||||
"tags_count": 93,
|
||||
"last_updated": "11 months ago",
|
||||
"url": "https://ollama.com/library/llama3.1"
|
||||
},
|
||||
{
|
||||
"name": "nomic-embed-text",
|
||||
"description": "A high-performing open embedding model with a large token context window",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": [],
|
||||
"pulls": "44.3M",
|
||||
"tags_count": 3,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/nomic-embed-text"
|
||||
},
|
||||
{
|
||||
"name": "llama3.2",
|
||||
"description": "Meta's Llama 3.2 goes small with 1B and 3B models",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["1b", "3b"],
|
||||
"pulls": "42.6M",
|
||||
"tags_count": 63,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama3.2"
|
||||
},
|
||||
{
|
||||
"name": "mistral",
|
||||
"description": "The 7B model released by Mistral AI, updated to version 0.3",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "21.3M",
|
||||
"tags_count": 84,
|
||||
"last_updated": "3 months ago",
|
||||
"url": "https://ollama.com/library/mistral"
|
||||
},
|
||||
{
|
||||
"name": "qwen2.5",
|
||||
"description": "Qwen2.5 models are pretrained on Alibaba's latest large-scale dataset, encompassing up to 18 trillion tokens. The model supports up to 128K tokens and has multilingual support",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["0.5b", "1.5b", "3b", "7b", "14b", "32b", "72b"],
|
||||
"pulls": "15.8M",
|
||||
"tags_count": 133,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/qwen2.5"
|
||||
},
|
||||
{
|
||||
"name": "phi3",
|
||||
"description": "Phi-3 is a family of lightweight 3B (Mini) and 14B (Medium) state-of-the-art open models by Microsoft",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["3.8b", "14b"],
|
||||
"pulls": "12.5M",
|
||||
"tags_count": 72,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/phi3"
|
||||
},
|
||||
{
|
||||
"name": "llama3",
|
||||
"description": "Meta Llama 3: The most capable openly available LLM to date",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b", "70b"],
|
||||
"pulls": "11.5M",
|
||||
"tags_count": 68,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama3"
|
||||
},
|
||||
{
|
||||
"name": "llava",
|
||||
"description": "LLaVA is a novel end-to-end trained large multimodal model that combines a vision encoder and Vicuna for general-purpose visual and language understanding. Updated to version 1.6",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["7b", "13b", "34b"],
|
||||
"pulls": "11.1M",
|
||||
"tags_count": 98,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llava"
|
||||
},
|
||||
{
|
||||
"name": "gemma2",
|
||||
"description": "Google Gemma 2 is a high-performing and efficient model available in three sizes: 2B, 9B, and 27B",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["2b", "9b", "27b"],
|
||||
"pulls": "8.6M",
|
||||
"tags_count": 94,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/gemma2"
|
||||
},
|
||||
{
|
||||
"name": "qwen2.5-coder",
|
||||
"description": "The latest series of Code-Specific Qwen models, with significant improvements in code generation, code reasoning, and code fixing",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["0.5b", "1.5b", "3b", "7b", "14b", "32b"],
|
||||
"pulls": "7.9M",
|
||||
"tags_count": 199,
|
||||
"last_updated": "5 months ago",
|
||||
"url": "https://ollama.com/library/qwen2.5-coder"
|
||||
},
|
||||
{
|
||||
"name": "phi4",
|
||||
"description": "Phi-4 is a 14B parameter, state-of-the-art open model from Microsoft",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["14b"],
|
||||
"pulls": "5.8M",
|
||||
"tags_count": 5,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/phi4"
|
||||
},
|
||||
{
|
||||
"name": "gemma",
|
||||
"description": "Gemma is a family of lightweight, state-of-the-art open models built by Google DeepMind. Updated to version 1.1",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["2b", "7b"],
|
||||
"pulls": "5.4M",
|
||||
"tags_count": 102,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/gemma"
|
||||
},
|
||||
{
|
||||
"name": "mxbai-embed-large",
|
||||
"description": "State-of-the-art large embedding model from mixedbread.ai",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": ["335m"],
|
||||
"pulls": "5.2M",
|
||||
"tags_count": 4,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/mxbai-embed-large"
|
||||
},
|
||||
{
|
||||
"name": "qwen",
|
||||
"description": "Qwen 1.5 is a series of large language models by Alibaba Cloud spanning from 0.5B to 110B parameters",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["0.5b", "1.8b", "4b", "7b", "14b", "32b", "72b", "110b"],
|
||||
"pulls": "5M",
|
||||
"tags_count": 379,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/qwen"
|
||||
},
|
||||
{
|
||||
"name": "qwen2",
|
||||
"description": "Qwen2 is a new series of large language models from Alibaba group",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["0.5b", "1.5b", "7b", "72b"],
|
||||
"pulls": "4.4M",
|
||||
"tags_count": 97,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/qwen2"
|
||||
},
|
||||
{
|
||||
"name": "llama2",
|
||||
"description": "Llama 2 is a collection of foundation language models ranging from 7B to 70B parameters",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "13b", "70b"],
|
||||
"pulls": "4.4M",
|
||||
"tags_count": 102,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama2"
|
||||
},
|
||||
{
|
||||
"name": "minicpm-v",
|
||||
"description": "A series of multimodal LLMs (MLLMs) designed for vision-language understanding",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "3.9M",
|
||||
"tags_count": 17,
|
||||
"last_updated": "11 months ago",
|
||||
"url": "https://ollama.com/library/minicpm-v"
|
||||
},
|
||||
{
|
||||
"name": "dolphin3",
|
||||
"description": "Dolphin 3.0 Llama 3.1 8B is the next generation of the Dolphin series of instruct-tuned models designed to be the ultimate general purpose local model",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "3.4M",
|
||||
"tags_count": 5,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/dolphin3"
|
||||
},
|
||||
{
|
||||
"name": "codellama",
|
||||
"description": "A large language model that can use text prompts to generate and discuss code",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "13b", "34b", "70b"],
|
||||
"pulls": "3.3M",
|
||||
"tags_count": 199,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/codellama"
|
||||
},
|
||||
{
|
||||
"name": "olmo2",
|
||||
"description": "OLMo 2 is a new family of 7B and 13B models trained on up to 5T tokens",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "13b"],
|
||||
"pulls": "3.3M",
|
||||
"tags_count": 9,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/olmo2"
|
||||
},
|
||||
{
|
||||
"name": "tinyllama",
|
||||
"description": "The TinyLlama project is an open endeavor to train a compact 1.1B Llama model on 3 trillion tokens",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["1.1b"],
|
||||
"pulls": "3M",
|
||||
"tags_count": 36,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/tinyllama"
|
||||
},
|
||||
{
|
||||
"name": "llama3.2-vision",
|
||||
"description": "Llama 3.2 Vision is a collection of instruction-tuned image reasoning generative models in 11B and 90B sizes",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["11b", "90b"],
|
||||
"pulls": "2.8M",
|
||||
"tags_count": 9,
|
||||
"last_updated": "5 months ago",
|
||||
"url": "https://ollama.com/library/llama3.2-vision"
|
||||
},
|
||||
{
|
||||
"name": "mistral-nemo",
|
||||
"description": "A state-of-the-art 12B model with 128k context length, built by Mistral AI in collaboration with NVIDIA",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["12b"],
|
||||
"pulls": "2.8M",
|
||||
"tags_count": 17,
|
||||
"last_updated": "3 months ago",
|
||||
"url": "https://ollama.com/library/mistral-nemo"
|
||||
},
|
||||
{
|
||||
"name": "llama3.3",
|
||||
"description": "New state of the art 70B model. Llama 3.3 70B offers similar performance compared to the Llama 3.1 405B model",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["70b"],
|
||||
"pulls": "2.6M",
|
||||
"tags_count": 14,
|
||||
"last_updated": "10 months ago",
|
||||
"url": "https://ollama.com/library/llama3.3"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-v3",
|
||||
"description": "A strong Mixture-of-Experts (MoE) language model with 671B total parameters with 37B activated for each token",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["671b"],
|
||||
"pulls": "2.6M",
|
||||
"tags_count": 5,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/deepseek-v3"
|
||||
},
|
||||
{
|
||||
"name": "bge-m3",
|
||||
"description": "BGE-M3 is a new model from BAAI distinguished for its versatility in Multi-Functionality, Multi-Linguality, and Multi-Granularity",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": ["567m"],
|
||||
"pulls": "2.6M",
|
||||
"tags_count": 3,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/bge-m3"
|
||||
},
|
||||
{
|
||||
"name": "mistral-small",
|
||||
"description": "Mistral Small 3 sets a new benchmark in the small Large Language Models category below 70B",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["22b", "24b"],
|
||||
"pulls": "2.1M",
|
||||
"tags_count": 21,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/mistral-small"
|
||||
},
|
||||
{
|
||||
"name": "smollm2",
|
||||
"description": "SmolLM2 is a family of compact language models available in three size: 135M, 360M, and 1.7B parameters",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["135m", "360m", "1.7b"],
|
||||
"pulls": "2M",
|
||||
"tags_count": 49,
|
||||
"last_updated": "12 months ago",
|
||||
"url": "https://ollama.com/library/smollm2"
|
||||
},
|
||||
{
|
||||
"name": "llava-llama3",
|
||||
"description": "A LLaVA model fine-tuned from Llama 3 Instruct with better scores in several benchmarks",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "2M",
|
||||
"tags_count": 4,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llava-llama3"
|
||||
},
|
||||
{
|
||||
"name": "all-minilm",
|
||||
"description": "Embedding models on very large sentence level datasets",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": ["22m", "33m"],
|
||||
"pulls": "1.9M",
|
||||
"tags_count": 10,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/all-minilm"
|
||||
},
|
||||
{
|
||||
"name": "qwq",
|
||||
"description": "QwQ is the reasoning model of the Qwen series, focused on advancing AI reasoning capabilities",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["32b"],
|
||||
"pulls": "1.7M",
|
||||
"tags_count": 17,
|
||||
"last_updated": "7 months ago",
|
||||
"url": "https://ollama.com/library/qwq"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-coder",
|
||||
"description": "DeepSeek Coder is a capable coding model trained on two trillion code and natural language tokens",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["1.3b", "6.7b", "33b"],
|
||||
"pulls": "1.6M",
|
||||
"tags_count": 102,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/deepseek-coder"
|
||||
},
|
||||
{
|
||||
"name": "starcoder2",
|
||||
"description": "StarCoder2 is the next generation of transparently trained open code LLMs that comes in three sizes: 3B, 7B and 15B parameters",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["3b", "7b", "15b"],
|
||||
"pulls": "1.4M",
|
||||
"tags_count": 67,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/starcoder2"
|
||||
},
|
||||
{
|
||||
"name": "mixtral",
|
||||
"description": "A set of Mixture of Experts (MoE) model with open weights by Mistral AI in 8x7b and 8x22b parameter sizes",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["8x7b", "8x22b"],
|
||||
"pulls": "1.4M",
|
||||
"tags_count": 70,
|
||||
"last_updated": "10 months ago",
|
||||
"url": "https://ollama.com/library/mixtral"
|
||||
},
|
||||
{
|
||||
"name": "llama2-uncensored",
|
||||
"description": "Uncensored Llama 2 model by George Sung and Jarrad Hope, with support for a 16K context window",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "70b"],
|
||||
"pulls": "1.3M",
|
||||
"tags_count": 34,
|
||||
"last_updated": "2 years ago",
|
||||
"url": "https://ollama.com/library/llama2-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "codegemma",
|
||||
"description": "CodeGemma is a collection of powerful, lightweight models that can perform a variety of coding tasks",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["2b", "7b"],
|
||||
"pulls": "1.3M",
|
||||
"tags_count": 85,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/codegemma"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-coder-v2",
|
||||
"description": "An open-source Mixture-of-Experts code language model that achieves performance comparable to GPT4-Turbo in code-specific tasks",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["16b", "236b"],
|
||||
"pulls": "1.2M",
|
||||
"tags_count": 64,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/deepseek-coder-v2"
|
||||
},
|
||||
{
|
||||
"name": "falcon3",
|
||||
"description": "A family of efficient AI models under 10B parameters performant in science, math, and coding through innovative training techniques",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["1b", "3b", "7b", "10b"],
|
||||
"pulls": "1M",
|
||||
"tags_count": 17,
|
||||
"last_updated": "10 months ago",
|
||||
"url": "https://ollama.com/library/falcon3"
|
||||
},
|
||||
{
|
||||
"name": "granite3.1-moe",
|
||||
"description": "The IBM Granite 1B and 3B models are long-context mixture of experts (MoE) Granite models from IBM designed for low latency usage",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["1b", "3b"],
|
||||
"pulls": "1M",
|
||||
"tags_count": 33,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/granite3.1-moe"
|
||||
},
|
||||
{
|
||||
"name": "qwen2.5vl",
|
||||
"description": "Flagship vision-language model of Qwen and also a significant leap from the previous Qwen2-VL",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["3b", "7b", "32b", "72b"],
|
||||
"pulls": "991K",
|
||||
"tags_count": 17,
|
||||
"last_updated": "5 months ago",
|
||||
"url": "https://ollama.com/library/qwen2.5vl"
|
||||
},
|
||||
{
|
||||
"name": "snowflake-arctic-embed",
|
||||
"description": "A suite of text embedding models by Snowflake, optimized for performance",
|
||||
"capabilities": ["embedding"],
|
||||
"parameter_sizes": ["22m", "33m", "110m", "137m", "335m"],
|
||||
"pulls": "951.1K",
|
||||
"tags_count": 16,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/snowflake-arctic-embed"
|
||||
},
|
||||
{
|
||||
"name": "orca-mini",
|
||||
"description": "A general-purpose model ranging from 3 billion parameters to 70 billion, suitable for entry-level hardware",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["3b", "7b", "13b", "70b"],
|
||||
"pulls": "930.6K",
|
||||
"tags_count": 119,
|
||||
"last_updated": "2 years ago",
|
||||
"url": "https://ollama.com/library/orca-mini"
|
||||
},
|
||||
{
|
||||
"name": "llama4",
|
||||
"description": "Meta's latest collection of multimodal models",
|
||||
"capabilities": ["vision", "tools"],
|
||||
"parameter_sizes": ["16x17b", "128x17b"],
|
||||
"pulls": "751.9K",
|
||||
"tags_count": 11,
|
||||
"last_updated": "4 months ago",
|
||||
"url": "https://ollama.com/library/llama4"
|
||||
},
|
||||
{
|
||||
"name": "phi",
|
||||
"description": "Phi-2: a 2.7B language model by Microsoft Research that demonstrates outstanding reasoning and language understanding capabilities",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["2.7b"],
|
||||
"pulls": "730.7K",
|
||||
"tags_count": 18,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/phi"
|
||||
},
|
||||
{
|
||||
"name": "mistral-small3.2",
|
||||
"description": "An update to Mistral Small that improves on function calling, instruction following, and less repetition errors",
|
||||
"capabilities": ["vision", "tools"],
|
||||
"parameter_sizes": ["24b"],
|
||||
"pulls": "718.3K",
|
||||
"tags_count": 5,
|
||||
"last_updated": "4 months ago",
|
||||
"url": "https://ollama.com/library/mistral-small3.2"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-mixtral",
|
||||
"description": "Uncensored, 8x7b and 8x22b fine-tuned models based on the Mixtral mixture of experts models that excels at coding tasks. Created by Eric Hartford",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["8x7b", "8x22b"],
|
||||
"pulls": "716.8K",
|
||||
"tags_count": 70,
|
||||
"last_updated": "10 months ago",
|
||||
"url": "https://ollama.com/library/dolphin-mixtral"
|
||||
},
|
||||
{
|
||||
"name": "gemma3n",
|
||||
"description": "Gemma 3n models are designed for efficient execution on everyday devices such as laptops, tablets or phones",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["e2b", "e4b"],
|
||||
"pulls": "670.3K",
|
||||
"tags_count": 9,
|
||||
"last_updated": "4 months ago",
|
||||
"url": "https://ollama.com/library/gemma3n"
|
||||
},
|
||||
{
|
||||
"name": "granite3.3",
|
||||
"description": "IBM Granite 2B and 8B models are 128K context length language models that have been fine-tuned for improved reasoning and instruction-following capabilities",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["2b", "8b"],
|
||||
"pulls": "660.7K",
|
||||
"tags_count": 3,
|
||||
"last_updated": "6 months ago",
|
||||
"url": "https://ollama.com/library/granite3.3"
|
||||
},
|
||||
{
|
||||
"name": "cogito",
|
||||
"description": "Cogito v1 Preview is a family of hybrid reasoning models by Deep Cogito that outperform the best available open models of the same size",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["3b", "8b", "14b", "32b", "70b"],
|
||||
"pulls": "659K",
|
||||
"tags_count": 20,
|
||||
"last_updated": "6 months ago",
|
||||
"url": "https://ollama.com/library/cogito"
|
||||
},
|
||||
{
|
||||
"name": "phi4-reasoning",
|
||||
"description": "Phi 4 reasoning and reasoning plus are 14-billion parameter open-weight reasoning models that rival much larger models on complex reasoning tasks",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["14b"],
|
||||
"pulls": "625.6K",
|
||||
"tags_count": 9,
|
||||
"last_updated": "6 months ago",
|
||||
"url": "https://ollama.com/library/phi4-reasoning"
|
||||
},
|
||||
{
|
||||
"name": "openthinker",
|
||||
"description": "A fully open-source family of reasoning models built using a dataset derived by distilling DeepSeek-R1",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["7b", "32b"],
|
||||
"pulls": "604.4K",
|
||||
"tags_count": 15,
|
||||
"last_updated": "6 months ago",
|
||||
"url": "https://ollama.com/library/openthinker"
|
||||
},
|
||||
{
|
||||
"name": "magistral",
|
||||
"description": "Magistral is a small, efficient reasoning model with 24B parameters, excelling in domain-specific, transparent, and multilingual reasoning",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["24b"],
|
||||
"pulls": "583.4K",
|
||||
"tags_count": 5,
|
||||
"last_updated": "4 months ago",
|
||||
"url": "https://ollama.com/library/magistral"
|
||||
},
|
||||
{
|
||||
"name": "deepscaler",
|
||||
"description": "A fine-tuned version of Deepseek-R1-Distilled-Qwen-1.5B that surpasses the performance of OpenAI's o1-preview with just 1.5B parameters",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["1.5b"],
|
||||
"pulls": "573.2K",
|
||||
"tags_count": 5,
|
||||
"last_updated": "8 months ago",
|
||||
"url": "https://ollama.com/library/deepscaler"
|
||||
},
|
||||
{
|
||||
"name": "codestral",
|
||||
"description": "Codestral is Mistral AI's first-ever code model designed for code generation tasks",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["22b"],
|
||||
"pulls": "503.9K",
|
||||
"tags_count": 17,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/codestral"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-phi",
|
||||
"description": "2.7B uncensored Dolphin model by Eric Hartford, based on the Phi language model by Microsoft Research",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["2.7b"],
|
||||
"pulls": "569.2K",
|
||||
"tags_count": 15,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/dolphin-phi"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-mistral",
|
||||
"description": "The uncensored Dolphin model based on Mistral that excels at coding tasks. Updated to version 2.8",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "409.3K",
|
||||
"tags_count": 120,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/dolphin-mistral"
|
||||
},
|
||||
{
|
||||
"name": "wizard-vicuna-uncensored",
|
||||
"description": "Wizard Vicuna Uncensored is a 7B, 13B, and 30B parameter model based on Llama 2 uncensored by Eric Hartford",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "13b", "30b"],
|
||||
"pulls": "255.1K",
|
||||
"tags_count": 49,
|
||||
"last_updated": "2 years ago",
|
||||
"url": "https://ollama.com/library/wizard-vicuna-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-starcoder2",
|
||||
"description": "A 7B and 15B uncensored variant of the Dolphin model family that excels at coding, based on StarCoder2",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["7b", "15b"],
|
||||
"pulls": "113.1K",
|
||||
"tags_count": 35,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/dolphin-starcoder2"
|
||||
},
|
||||
{
|
||||
"name": "wizardlm-uncensored",
|
||||
"description": "Uncensored version of Wizard LM model",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b", "13b"],
|
||||
"pulls": "89.5K",
|
||||
"tags_count": 18,
|
||||
"last_updated": "2 years ago",
|
||||
"url": "https://ollama.com/library/wizardlm-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "llama-3.2-3b-instruct-abliterated",
|
||||
"description": "An uncensored version of the original Llama-3.2-3B-Instruct, created via abliteration",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["3b"],
|
||||
"pulls": "508.1K",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama-3.2-3b-instruct-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "josiefied-qwen3-uncensored",
|
||||
"description": "Qwen3, but Josiefied and uncensored",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "71.5K",
|
||||
"tags_count": 47,
|
||||
"last_updated": "4 months ago",
|
||||
"url": "https://ollama.com/library/josiefied-qwen3-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "abliterated-llama3.1-8b",
|
||||
"description": "Ablitered v3 llama-3.1 8b with uncensored prompt",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "31K",
|
||||
"tags_count": 38,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/abliterated-llama3.1-8b"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-uncensored",
|
||||
"description": "Dolphin is an uncensored multilingual chat tuned model by Eric Hartford, conformant to system prompt, good at coding and various tasks",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "25.5K",
|
||||
"tags_count": 15,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/dolphin-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "specialized-uncensored-openai-20b",
|
||||
"description": "Specialized uncensored/abliterated quants for new OpenAI 20B MOE - Mixture of Experts Model at 80+ T/S (quantized Q5_1)",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["20b"],
|
||||
"pulls": "22.9K",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 month ago",
|
||||
"url": "https://ollama.com/library/specialized-uncensored-openai-20b"
|
||||
},
|
||||
{
|
||||
"name": "lexi-uncensored",
|
||||
"description": "Lexi is uncensored, which makes the model compliant. You are advised to implement your own alignment layer before exposing the model as a service",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "15.3K",
|
||||
"tags_count": 9,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/lexi-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "llama3-uncensored",
|
||||
"description": "Uncensored version of Llama3 8b",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "13.6K",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama3-uncensored"
|
||||
},
|
||||
{
|
||||
"name": "llama3-uncensored-long",
|
||||
"description": "This model was created with the goal for a good llama 3 uncencored model with long context",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "13.5K",
|
||||
"tags_count": 4,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/llama3-uncensored-long"
|
||||
},
|
||||
{
|
||||
"name": "deepseek-r1-distilled",
|
||||
"description": "DeepSeek's first generation reasoning models with comparable performance to OpenAI-o1",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "586.4K",
|
||||
"tags_count": 55,
|
||||
"last_updated": "5 months ago",
|
||||
"url": "https://ollama.com/library/deepseek-r1-distilled"
|
||||
},
|
||||
{
|
||||
"name": "llama-3.2-11b-vision-abliterated",
|
||||
"description": "From huihui-ai/Llama-3.2-11B-Vision-Instruct-abliterated",
|
||||
"capabilities": ["vision"],
|
||||
"parameter_sizes": ["11b"],
|
||||
"pulls": "69.8K",
|
||||
"tags_count": 2,
|
||||
"last_updated": "8 months ago",
|
||||
"url": "https://ollama.com/library/llama-3.2-11b-vision-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "qwen2.5-1m",
|
||||
"description": "Qwen2.5-1M is the long-context version of the Qwen2.5 series models, supporting a context length of up to 1M tokens",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "31K",
|
||||
"tags_count": 11,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/qwen2.5-1m"
|
||||
},
|
||||
{
|
||||
"name": "qwen3-abliterated",
|
||||
"description": "This is the abliterated version of Qwen3, which is the latest generation of large language models in Qwen series",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "23.9K",
|
||||
"tags_count": 6,
|
||||
"last_updated": "5 months ago",
|
||||
"url": "https://ollama.com/library/qwen3-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "dolphin-3.0-llama3.1-8b-abliterated",
|
||||
"description": "Dolphin 3.0 Llama 3.1 8B is the next generation of the Dolphin series of instruct-tuned models designed to be the ultimate general purpose local model",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "21.7K",
|
||||
"tags_count": 5,
|
||||
"last_updated": "9 months ago",
|
||||
"url": "https://ollama.com/library/dolphin-3.0-llama3.1-8b-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "gemma-3n-abliterated",
|
||||
"description": "This is an uncensored version of google/gemma-3n created with abliteration",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["9b"],
|
||||
"pulls": "13.4K",
|
||||
"tags_count": 2,
|
||||
"last_updated": "3 months ago",
|
||||
"url": "https://ollama.com/library/gemma-3n-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "neuraldaredevil-8b-abliterated",
|
||||
"description": "Contains fp16 version of the excellent NeuralDaredevil-8B-abliterated",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "9,677",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/neuraldaredevil-8b-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "mlabonne-neuraldaredevil-8b-abliterated",
|
||||
"description": "mlabonne/NeuralDaredevil-8B-abliterated",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["8b"],
|
||||
"pulls": "7,679",
|
||||
"tags_count": 4,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/mlabonne-neuraldaredevil-8b-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "phi3-medium-abliterated",
|
||||
"description": "This is a phi3 medium model that has had the prompt rejection neurons snipped which is better than fine tuning",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["14b"],
|
||||
"pulls": "6,591",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/phi3-medium-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "gemma2-9b-abliterated",
|
||||
"description": "Abliterated version of Google's Gemma 2 9B",
|
||||
"capabilities": [],
|
||||
"parameter_sizes": ["9b"],
|
||||
"pulls": "6,386",
|
||||
"tags_count": 1,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/gemma2-9b-abliterated"
|
||||
},
|
||||
{
|
||||
"name": "codeqwen1.5",
|
||||
"description": "CodeQwen1.5 is a large language model pretrained on a large amount of code data",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["7b"],
|
||||
"pulls": "182K",
|
||||
"tags_count": 30,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/codeqwen1.5"
|
||||
},
|
||||
{
|
||||
"name": "deepcoder",
|
||||
"description": "DeepCoder is a fully open-Source 14B coder model at O3-mini level, with a 1.5B version also available",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["1.5b", "14b"],
|
||||
"pulls": "352.4K",
|
||||
"tags_count": 9,
|
||||
"last_updated": "6 months ago",
|
||||
"url": "https://ollama.com/library/deepcoder"
|
||||
},
|
||||
{
|
||||
"name": "granite-code",
|
||||
"description": "A family of open foundation models by IBM for Code Intelligence",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["8b", "20b", "34b"],
|
||||
"pulls": "295.1K",
|
||||
"tags_count": 162,
|
||||
"last_updated": "1 year ago",
|
||||
"url": "https://ollama.com/library/granite-code"
|
||||
},
|
||||
{
|
||||
"name": "mistral-large-2",
|
||||
"description": "Mistral Large 2 is Mistral's new flagship model that is significantly more capable in code generation, mathematics, and reasoning",
|
||||
"capabilities": ["tools", "thinking"],
|
||||
"parameter_sizes": ["123b"],
|
||||
"pulls": "265.9K",
|
||||
"tags_count": 32,
|
||||
"last_updated": "11 months ago",
|
||||
"url": "https://ollama.com/library/mistral-large-2"
|
||||
},
|
||||
{
|
||||
"name": "starcoder",
|
||||
"description": "StarCoder is a code generation model trained on 80+ programming languages",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["15b"],
|
||||
"pulls": "228.8K",
|
||||
"tags_count": 100,
|
||||
"last_updated": "2 years ago",
|
||||
"url": "https://ollama.com/library/starcoder"
|
||||
},
|
||||
{
|
||||
"name": "opencoder",
|
||||
"description": "OpenCoder is an open and reproducible code LLM family which includes 1.5B and 8B models, supporting chat in English and Chinese languages",
|
||||
"capabilities": ["tools"],
|
||||
"parameter_sizes": ["1.5b", "8b"],
|
||||
"pulls": "194.3K",
|
||||
"tags_count": 9,
|
||||
"last_updated": "11 months ago",
|
||||
"url": "https://ollama.com/library/opencoder"
|
||||
},
|
||||
{
|
||||
"name": "llama-3.2-1b-abliterated",
|
||||
"description": "Abliterated version for small reasoning tasks from Llama 3.2 1B",
|
||||
"capabilities": ["thinking"],
|
||||
"parameter_sizes": ["1b"],
|
||||
"pulls": "39.9K",
|
||||
"tags_count": 16,
|
||||
"last_updated": "2 months ago",
|
||||
"url": "https://ollama.com/library/llama-3.2-1b-abliterated"
|
||||
}
|
||||
],
|
||||
"api_information": {
|
||||
"local_api_endpoints": [
|
||||
{
|
||||
"endpoint": "/api/tags",
|
||||
"method": "GET",
|
||||
"description": "List all locally installed models"
|
||||
}
|
||||
],
|
||||
"limitations": {
|
||||
"remote_listing": "Não existe endpoint público para listar todos os modelos disponíveis no registry remoto do Ollama",
|
||||
"user_models": "Modelos de usuários (uncensored/abliterated) são variantes comunitárias; busque via /search",
|
||||
"reasoning_focus": "Modelos com 'thinking' são otimizados para raciocínio multi-step (ex: CoT, logical puzzles)"
|
||||
}
|
||||
},
|
||||
"usage_instructions": {
|
||||
"list_local_models": "curl http://localhost:11434/api/tags",
|
||||
"pull_model": "ollama pull <model_name> (ex: ollama pull deepseek-r1 para reasoning)",
|
||||
"test_uncensored": "ollama run llama2-uncensored --prompt 'Gere código Python sem restrições'",
|
||||
"test_reasoning": "ollama run phi4-reasoning --prompt 'Resolva: 2+2=?'",
|
||||
"browse_searches": "https://ollama.com/search?q=<termo> (ex: uncensored, code)"
|
||||
},
|
||||
"recommendations_for_devs": {
|
||||
"coding": "Use qwen2.5-coder ou codestral para geração de código avançada [web:3]",
|
||||
"reasoning": "deepseek-r1 ou phi4-reasoning para tarefas lógicas/math [web:77]",
|
||||
"uncensored": "llama2-uncensored para experimentos sem alinhamento ético, mas implemente safeguards [attached_file:1]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use actix_web::{get, post, web, HttpResponse, Responder};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::core::models::{pull_model, read_model_list};
|
||||
use crate::crypto::CryptoState;
|
||||
use crate::state::AppContext;
|
||||
use crate::utils::log;
|
||||
use crate::ws::{ws_route, BrokerAddr, WsEvent, WsEventKind};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
|
||||
#[get("/health")]
|
||||
pub async fn health() -> impl Responder {
|
||||
HttpResponse::Ok().json(serde_json::json!({"status":"ok"}))
|
||||
}
|
||||
|
||||
#[get("/api/models")]
|
||||
pub async fn models_list(state: web::Data<AppContext>) -> impl Responder {
|
||||
match read_model_list(&state.model_list_path) {
|
||||
Ok(list) => HttpResponse::Ok().json(list),
|
||||
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct InstallReq {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[post("/api/models/install")]
|
||||
pub async fn models_install(state: web::Data<AppContext>, payload: web::Json<InstallReq>) -> impl Responder {
|
||||
let name = payload.name.clone();
|
||||
let tx = state.broker.clone();
|
||||
let base = state.ollama_base_url.clone();
|
||||
let value = name.clone();
|
||||
actix_rt::spawn(async move {
|
||||
let _ = pull_model(&base, &value, &tx).await;
|
||||
});
|
||||
HttpResponse::Accepted().json(serde_json::json!({"status":"started","model":name}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetKeyReq { pub hex_key: String }
|
||||
|
||||
#[post("/api/crypto/set-key")]
|
||||
pub async fn crypto_set_key(state: web::Data<AppContext>, payload: web::Json<SetKeyReq>) -> impl Responder {
|
||||
match state.crypto.set_key_hex(&payload.hex_key) {
|
||||
Ok(()) => HttpResponse::Ok().json(serde_json::json!({"status":"ok"})),
|
||||
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/crypto/rotate-key")]
|
||||
pub async fn crypto_rotate_key(state: web::Data<AppContext>) -> impl Responder {
|
||||
match state.crypto.rotate_key() {
|
||||
Ok(hex) => HttpResponse::Ok().json(serde_json::json!({"key": hex})),
|
||||
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CryptReq { pub data: String }
|
||||
|
||||
#[post("/api/crypto/encrypt")]
|
||||
pub async fn crypto_encrypt(state: web::Data<AppContext>, payload: web::Json<CryptReq>) -> impl Responder {
|
||||
match state.crypto.encrypt(payload.data.as_bytes()) {
|
||||
Ok(ct) => HttpResponse::Ok().json(serde_json::json!({"ciphertext": general_purpose::STANDARD.encode(ct)})),
|
||||
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/crypto/decrypt")]
|
||||
pub async fn crypto_decrypt(state: web::Data<AppContext>, payload: web::Json<CryptReq>) -> impl Responder {
|
||||
match general_purpose::STANDARD.decode(&payload.data) {
|
||||
Ok(bytes) => match state.crypto.decrypt(&bytes) {
|
||||
Ok(pt) => HttpResponse::Ok().json(serde_json::json!({"plaintext": String::from_utf8_lossy(&pt)})),
|
||||
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
|
||||
},
|
||||
Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": format!("base64 decode: {}", e)})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig, state: web::Data<AppContext>) {
|
||||
cfg.app_data(state);
|
||||
cfg.service(health);
|
||||
cfg.service(models_list);
|
||||
cfg.service(models_install);
|
||||
cfg.service(crypto_set_key);
|
||||
cfg.service(crypto_rotate_key);
|
||||
cfg.service(crypto_encrypt);
|
||||
cfg.service(crypto_decrypt);
|
||||
}
|
||||
|
||||
pub fn ws_config(cfg: &mut web::ServiceConfig, broker: actix_web::web::Data<BrokerAddr>) {
|
||||
cfg.app_data(broker);
|
||||
cfg.service(ws_route);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod user_controller;
|
||||
@@ -0,0 +1,92 @@
|
||||
use actix_web::{get, post, put, delete, web, HttpResponse, Responder};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::middlewares::error::ApiError;
|
||||
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
|
||||
use crate::services::user_service::UserService;
|
||||
|
||||
#[derive(Deserialize, IntoParams, ToSchema)]
|
||||
pub struct UserPath { pub id: String }
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/users",
|
||||
responses(
|
||||
(status = 200, description = "Lista de usuários", body = [User])
|
||||
)
|
||||
)]
|
||||
#[get("/api/v1/users")]
|
||||
pub async fn list_users(svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> impl Responder {
|
||||
HttpResponse::Ok().json(svc.list())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/users/{id}",
|
||||
params(UserPath),
|
||||
responses(
|
||||
(status = 200, description = "Usuário encontrado", body = User),
|
||||
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
|
||||
)
|
||||
)]
|
||||
#[get("/api/v1/users/{id}")]
|
||||
pub async fn get_user(path: web::Path<UserPath>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
|
||||
match svc.get(path.id.clone()) {
|
||||
Some(u) => Ok(HttpResponse::Ok().json(u)),
|
||||
None => Err(ApiError::NotFound("Usuário não encontrado".into())),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/users",
|
||||
request_body = CreateUserRequest,
|
||||
responses(
|
||||
(status = 201, description = "Usuário criado", body = User),
|
||||
(status = 400, description = "Erro de validação", body = crate::middlewares::error::ApiErrorBody)
|
||||
)
|
||||
)]
|
||||
#[post("/api/v1/users")]
|
||||
pub async fn create_user(payload: web::Json<CreateUserRequest>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
|
||||
let mut req = payload.into_inner();
|
||||
if let Err(e) = req.validate() { return Err(ApiError::BadRequest(format!("validação: {}", e))); }
|
||||
let user = svc.create(req);
|
||||
Ok(HttpResponse::Created().json(user))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/users/{id}",
|
||||
params(UserPath),
|
||||
request_body = UpdateUserRequest,
|
||||
responses(
|
||||
(status = 200, description = "Usuário atualizado", body = User),
|
||||
(status = 400, description = "Erro de validação", body = crate::middlewares::error::ApiErrorBody),
|
||||
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
|
||||
)
|
||||
)]
|
||||
#[put("/api/v1/users/{id}")]
|
||||
pub async fn update_user(path: web::Path<UserPath>, payload: web::Json<UpdateUserRequest>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
|
||||
let mut req = payload.into_inner();
|
||||
if let Err(e) = req.validate() { return Err(ApiError::BadRequest(format!("validação: {}", e))); }
|
||||
match svc.update(path.id.clone(), req) {
|
||||
Some(u) => Ok(HttpResponse::Ok().json(u)),
|
||||
None => Err(ApiError::NotFound("Usuário não encontrado".into())),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/users/{id}",
|
||||
params(UserPath),
|
||||
responses(
|
||||
(status = 204, description = "Usuário deletado"),
|
||||
(status = 404, description = "Não encontrado", body = crate::middlewares::error::ApiErrorBody)
|
||||
)
|
||||
)]
|
||||
#[delete("/api/v1/users/{id}")]
|
||||
pub async fn delete_user(path: web::Path<UserPath>, svc: web::Data<UserService<crate::repositories::user_repository::InMemoryUserRepository>>) -> Result<impl Responder, ApiError> {
|
||||
if svc.delete(path.id.clone()) { Ok(HttpResponse::NoContent().finish()) } else { Err(ApiError::NotFound("Usuário não encontrado".into())) }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod models;
|
||||
@@ -0,0 +1,61 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use actix_web::http::header::USER_AGENT;
|
||||
use awc::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::log;
|
||||
use crate::ws::{WsEvent, WsEventKind};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ModelInfo {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
pub fn read_model_list(path: &str) -> Result<Vec<ModelInfo>, String> {
|
||||
let data = fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
let list: Vec<ModelInfo> = serde_json::from_str(&data).map_err(|e| e.to_string())?;
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub async fn pull_model(
|
||||
base_url: &str,
|
||||
model: &str,
|
||||
tx: &tokio::sync::broadcast::Sender<WsEvent>,
|
||||
) -> Result<(), String> {
|
||||
let client = Client::default();
|
||||
let url = format!("{}/api/pull", base_url.trim_end_matches('/'));
|
||||
let body = serde_json::json!({"name": model});
|
||||
let mut resp = client
|
||||
.post(url)
|
||||
.insert_header((USER_AGENT, "OllaHubBackend/1.0"))
|
||||
.send_json(&body)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let status = resp.status();
|
||||
let bytes = resp
|
||||
.body()
|
||||
.await
|
||||
.map_err(|e| format!("Erro corpo resposta: {}", e))?;
|
||||
if !status.is_success() {
|
||||
return Err(format!("Falha ao puxar modelo: {}", status));
|
||||
}
|
||||
let text = String::from_utf8_lossy(&bytes).to_string();
|
||||
for line in text.lines() {
|
||||
tx.send(WsEvent {
|
||||
kind: WsEventKind::Log,
|
||||
message: line.to_string(),
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
tx.send(WsEvent {
|
||||
kind: WsEventKind::Done,
|
||||
message: format!("Download do modelo '{}' concluído", model),
|
||||
})
|
||||
.ok();
|
||||
log::info(&format!("Modelo '{}' baixado com sucesso", model));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use aes_gcm::aead::{Aead, KeyInit};
|
||||
use aes_gcm::{Aes256Gcm, Nonce};
|
||||
use rand::RngCore;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::utils::log;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CryptoState {
|
||||
key: Arc<Mutex<Option<[u8; 32]>>>,
|
||||
key_path: PathBuf,
|
||||
}
|
||||
|
||||
impl CryptoState {
|
||||
pub fn new(key_path: PathBuf) -> Self {
|
||||
let state = Self {
|
||||
key: Arc::new(Mutex::new(None)),
|
||||
key_path,
|
||||
};
|
||||
state.load_key_from_disk();
|
||||
state
|
||||
}
|
||||
|
||||
fn load_key_from_disk(&self) {
|
||||
if self.key_path.exists() {
|
||||
if let Ok(hex) = fs::read_to_string(&self.key_path) {
|
||||
let hex = hex.trim();
|
||||
match hex::decode(hex) {
|
||||
Ok(bytes) if bytes.len() == 32 => {
|
||||
let mut arr = [0u8; 32];
|
||||
arr.copy_from_slice(&bytes);
|
||||
*self.key.lock().unwrap() = Some(arr);
|
||||
log::info("Chave carregada do disco");
|
||||
}
|
||||
_ => log::warn("Arquivo de chave inválido; ignorando"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_key_hex(&self, hex_key: &str) -> Result<(), String> {
|
||||
let bytes = hex::decode(hex_key).map_err(|e| format!("hex inválido: {}", e))?;
|
||||
if bytes.len() != 32 {
|
||||
return Err("Chave deve ter 32 bytes".into());
|
||||
}
|
||||
let mut arr = [0u8; 32];
|
||||
arr.copy_from_slice(&bytes);
|
||||
*self.key.lock().unwrap() = Some(arr);
|
||||
fs::create_dir_all(self.key_path.parent().unwrap_or_else(|| std::path::Path::new(".")))
|
||||
.map_err(|e| e.to_string())?;
|
||||
fs::write(&self.key_path, hex::encode(arr)).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rotate_key(&self) -> Result<String, String> {
|
||||
let mut new_key = [0u8; 32];
|
||||
rand::thread_rng().fill_bytes(&mut new_key);
|
||||
*self.key.lock().unwrap() = Some(new_key);
|
||||
fs::create_dir_all(self.key_path.parent().unwrap_or_else(|| std::path::Path::new(".")))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let hex = hex::encode(new_key);
|
||||
fs::write(&self.key_path, &hex).map_err(|e| e.to_string())?;
|
||||
Ok(hex)
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let key = self.key.lock().unwrap();
|
||||
let key = key.ok_or("Chave não definida")?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?;
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rand::thread_rng().fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
let mut out = Vec::from(nonce_bytes);
|
||||
let ct = cipher
|
||||
.encrypt(nonce, plaintext)
|
||||
.map_err(|e| format!("Erro ao criptografar: {}", e))?;
|
||||
out.extend_from_slice(&ct);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, String> {
|
||||
if ciphertext.len() < 12 {
|
||||
return Err("Ciphertext inválido".into());
|
||||
}
|
||||
let (nonce_bytes, ct) = ciphertext.split_at(12);
|
||||
let key = self.key.lock().unwrap();
|
||||
let key = key.ok_or("Chave não definida")?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key).map_err(|e| e.to_string())?;
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
let pt = cipher
|
||||
.decrypt(nonce, ct)
|
||||
.map_err(|e| format!("Erro ao descriptografar: {}", e))?;
|
||||
Ok(pt)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::CryptoState;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn crypto_roundtrip() {
|
||||
let tmp = env::temp_dir().join("crypto_key_test.hex");
|
||||
let crypto = CryptoState::new(PathBuf::from(&tmp));
|
||||
crypto.rotate_key().unwrap();
|
||||
let msg = b"hello";
|
||||
let ct = crypto.encrypt(msg).unwrap();
|
||||
let pt = crypto.decrypt(&ct).unwrap();
|
||||
assert_eq!(msg.to_vec(), pt);
|
||||
let _ = std::fs::remove_file(tmp);
|
||||
}
|
||||
}
|
||||
+1289
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,38 @@
|
||||
use actix_web::{dev::{ServiceRequest, ServiceResponse}, Error};
|
||||
use actix_web::{http::header, HttpResponse};
|
||||
use actix_web::middleware::Next;
|
||||
use actix_web::body::MessageBody;
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::state::AppContext;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Claims { sub: String, exp: usize, role: Option<String> }
|
||||
|
||||
pub async fn jwt_auth<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse, Error>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
// Apenas protege rotas da API v1; demais passam direto
|
||||
if !req.path().starts_with("/api/v1/") {
|
||||
let res = next.call(req).await?;
|
||||
return Ok(res.map_into_boxed_body());
|
||||
}
|
||||
let ctx = req.app_data::<actix_web::web::Data<AppContext>>().cloned();
|
||||
let Some(state) = ctx else { return Ok(req.into_response(HttpResponse::InternalServerError().finish())); };
|
||||
let Some(hdr) = req.headers().get(header::AUTHORIZATION) else {
|
||||
return Ok(req.into_response(HttpResponse::Unauthorized().finish()));
|
||||
};
|
||||
let token = hdr.to_str().unwrap_or("");
|
||||
let token = token.strip_prefix("Bearer ").unwrap_or(token);
|
||||
let secret = &state.jwt_secret;
|
||||
let res = decode::<Claims>(token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::default());
|
||||
match res {
|
||||
Ok(_data) => {
|
||||
let res = next.call(req).await?;
|
||||
Ok(res.map_into_boxed_body())
|
||||
}
|
||||
Err(_) => Ok(req.into_response(HttpResponse::Unauthorized().finish())),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ApiErrorBody { pub error: String }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
BadRequest(String),
|
||||
Unauthorized(String),
|
||||
NotFound(String),
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl ResponseError for ApiError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match self {
|
||||
ApiError::BadRequest(msg) => HttpResponse::BadRequest().json(ApiErrorBody { error: msg.clone() }),
|
||||
ApiError::Unauthorized(msg) => HttpResponse::Unauthorized().json(ApiErrorBody { error: msg.clone() }),
|
||||
ApiError::NotFound(msg) => HttpResponse::NotFound().json(ApiErrorBody { error: msg.clone() }),
|
||||
ApiError::Internal(msg) => HttpResponse::InternalServerError().json(ApiErrorBody { error: msg.clone() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ApiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ApiError::BadRequest(msg) => write!(f, "BadRequest: {}", msg),
|
||||
ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
|
||||
ApiError::NotFound(msg) => write!(f, "NotFound: {}", msg),
|
||||
ApiError::Internal(msg) => write!(f, "Internal: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod auth;
|
||||
pub mod error;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod user;
|
||||
@@ -0,0 +1,33 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn from_create(req: CreateUserRequest) -> Self {
|
||||
Self { id: Uuid::new_v4().to_string(), name: req.name, email: req.email }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
|
||||
pub struct CreateUserRequest {
|
||||
#[validate(length(min = 2))]
|
||||
pub name: String,
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
|
||||
pub struct UpdateUserRequest {
|
||||
#[validate(length(min = 2))]
|
||||
pub name: Option<String>,
|
||||
#[validate(email)]
|
||||
pub email: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod user_repository;
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
|
||||
|
||||
pub trait UserRepository: Send + Sync {
|
||||
fn list(&self) -> Vec<User>;
|
||||
fn get(&self, id: String) -> Option<User>;
|
||||
fn create(&self, req: CreateUserRequest) -> User;
|
||||
fn update(&self, id: String, req: UpdateUserRequest) -> Option<User>;
|
||||
fn delete(&self, id: String) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InMemoryUserRepository {
|
||||
inner: Arc<RwLock<HashMap<String, User>>>,
|
||||
}
|
||||
|
||||
impl InMemoryUserRepository {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: Arc::new(RwLock::new(HashMap::new())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserRepository for InMemoryUserRepository {
|
||||
fn list(&self) -> Vec<User> {
|
||||
self.inner.read().unwrap().values().cloned().collect()
|
||||
}
|
||||
|
||||
fn get(&self, id: String) -> Option<User> {
|
||||
self.inner.read().unwrap().get(&id).cloned()
|
||||
}
|
||||
|
||||
fn create(&self, req: CreateUserRequest) -> User {
|
||||
let user = User::from_create(req);
|
||||
self.inner.write().unwrap().insert(user.id.clone(), user.clone());
|
||||
user
|
||||
}
|
||||
|
||||
fn update(&self, id: String, req: UpdateUserRequest) -> Option<User> {
|
||||
let mut map = self.inner.write().unwrap();
|
||||
if let Some(existing) = map.get_mut(&id) {
|
||||
if let Some(name) = req.name { existing.name = name; }
|
||||
if let Some(email) = req.email { existing.email = email; }
|
||||
return Some(existing.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn delete(&self, id: String) -> bool {
|
||||
self.inner.write().unwrap().remove(&id).is_some()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use actix_web::web;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::controllers::user_controller::{list_users, get_user, create_user, update_user, delete_user};
|
||||
use crate::repositories::user_repository::InMemoryUserRepository;
|
||||
use crate::services::user_service::UserService;
|
||||
use crate::state::AppContext;
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::controllers::user_controller::list_users,
|
||||
crate::controllers::user_controller::get_user,
|
||||
crate::controllers::user_controller::create_user,
|
||||
crate::controllers::user_controller::update_user,
|
||||
crate::controllers::user_controller::delete_user,
|
||||
),
|
||||
components(schemas(
|
||||
crate::models::user::User,
|
||||
crate::models::user::CreateUserRequest,
|
||||
crate::models::user::UpdateUserRequest,
|
||||
crate::middlewares::error::ApiErrorBody,
|
||||
)),
|
||||
tags(
|
||||
(name = "users", description = "Gerenciamento de usuários"),
|
||||
)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
pub fn configure_v1(cfg: &mut web::ServiceConfig, _state: web::Data<AppContext>) {
|
||||
let repo = InMemoryUserRepository::new();
|
||||
let svc = UserService::new(repo);
|
||||
cfg.app_data(web::Data::new(svc));
|
||||
cfg.service(list_users);
|
||||
cfg.service(get_user);
|
||||
cfg.service(create_user);
|
||||
cfg.service(update_user);
|
||||
cfg.service(delete_user);
|
||||
}
|
||||
|
||||
pub fn swagger_ui() -> SwaggerUi {
|
||||
SwaggerUi::new("/docs").url("/api-doc/openapi.json", ApiDoc::openapi())
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod system;
|
||||
pub mod ollama;
|
||||
pub mod user_service;
|
||||
@@ -0,0 +1,18 @@
|
||||
use actix_web::http::header::USER_AGENT;
|
||||
use awc::Client;
|
||||
|
||||
pub async fn check_api(base_url: &str) -> Result<(), String> {
|
||||
let client = Client::default();
|
||||
let url = format!("{}/api/tags", base_url.trim_end_matches('/'));
|
||||
let resp = client
|
||||
.get(url)
|
||||
.insert_header((USER_AGENT, "OllaHubBackend/1.0"))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if resp.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("API ollama indisponível: {}", resp.status()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Platform {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOS,
|
||||
}
|
||||
|
||||
pub fn detect_platform() -> Platform {
|
||||
if cfg!(target_os = "windows") {
|
||||
Platform::Windows
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Platform::MacOS
|
||||
} else {
|
||||
Platform::Linux
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_cmd(cmd: &str, args: &[&str]) -> Result<String, String> {
|
||||
let output = Command::new(cmd).args(args).output().map_err(|e| e.to_string())?;
|
||||
if output.status.success() {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
} else {
|
||||
Err(String::from_utf8_lossy(&output.stderr).to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::models::user::{CreateUserRequest, UpdateUserRequest, User};
|
||||
use crate::repositories::user_repository::UserRepository;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserService<R: UserRepository> {
|
||||
repo: R,
|
||||
}
|
||||
|
||||
impl<R: UserRepository> UserService<R> {
|
||||
pub fn new(repo: R) -> Self { Self { repo } }
|
||||
|
||||
pub fn list(&self) -> Vec<User> { self.repo.list() }
|
||||
pub fn get(&self, id: String) -> Option<User> { self.repo.get(id) }
|
||||
pub fn create(&self, req: CreateUserRequest) -> User { self.repo.create(req) }
|
||||
pub fn update(&self, id: String, req: UpdateUserRequest) -> Option<User> { self.repo.update(id, req) }
|
||||
pub fn delete(&self, id: String) -> bool { self.repo.delete(id) }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::crypto::CryptoState;
|
||||
use crate::ws::WsEvent;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppContext {
|
||||
pub broker: broadcast::Sender<WsEvent>,
|
||||
pub crypto: Arc<CryptoState>,
|
||||
pub ollama_base_url: String,
|
||||
pub model_list_path: String,
|
||||
pub jwt_secret: String,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
pub fn new(ollama_base_url: String, model_list_path: String, key_path: PathBuf) -> Self {
|
||||
let broker = crate::ws::new_broker();
|
||||
let crypto = Arc::new(CryptoState::new(key_path));
|
||||
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "changeme-dev-secret".into());
|
||||
Self {
|
||||
broker,
|
||||
crypto,
|
||||
ollama_base_url,
|
||||
model_list_path,
|
||||
jwt_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub fn now_ts() -> u128 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis()
|
||||
}
|
||||
|
||||
pub fn info(message: &str) {
|
||||
println!("[INFO] [{}] {}", now_ts(), message);
|
||||
}
|
||||
|
||||
pub fn warn(message: &str) {
|
||||
eprintln!("[WARN] [{}] {}", now_ts(), message);
|
||||
}
|
||||
|
||||
pub fn error(message: &str) {
|
||||
eprintln!("[ERROR] [{}] {}", now_ts(), message);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod log;
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use actix::{Actor, Context, Handler, ActorContext, Message};
|
||||
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
|
||||
use actix_web_actors::ws;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::utils::log;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum WsEventKind {
|
||||
Log,
|
||||
Progress,
|
||||
Done,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WsEvent {
|
||||
pub kind: WsEventKind,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub fn new_broker() -> broadcast::Sender<WsEvent> {
|
||||
let (tx, _rx) = broadcast::channel(64);
|
||||
tx
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrokerAddr(pub broadcast::Sender<WsEvent>);
|
||||
|
||||
pub struct WsSession {
|
||||
pub broker: broadcast::Sender<WsEvent>,
|
||||
}
|
||||
|
||||
impl Actor for WsSession {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
let mut rx = self.broker.subscribe();
|
||||
// Forward broker messages to the websocket client
|
||||
actix_rt::spawn(async move {
|
||||
while let Ok(evt) = rx.recv().await {
|
||||
// We cannot access ctx here; use an internal channel.
|
||||
// Instead, send events via a helper actor message.
|
||||
// This is handled below using a Trampoline handler.
|
||||
// For simplicity, we log; WS pushes are driven by incoming pings.
|
||||
log::info(&format!("WS evt: {:?}: {}", evt.kind, evt.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct PushEvent(pub WsEvent);
|
||||
|
||||
impl Handler<PushEvent> for WsSession {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: PushEvent, ctx: &mut Self::Context) -> Self::Result {
|
||||
let payload = serde_json::to_string(&msg.0).unwrap_or_else(|_| "{\"kind\":\"Error\",\"message\":\"serialize\"}".into());
|
||||
ctx.text(payload);
|
||||
}
|
||||
}
|
||||
|
||||
impl actix::StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsSession {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
Ok(ws::Message::Ping(msg)) => {
|
||||
ctx.pong(&msg);
|
||||
}
|
||||
Ok(ws::Message::Text(text)) => {
|
||||
// Echo and also subscribe client to stream by pushing recent event
|
||||
ctx.text(text);
|
||||
}
|
||||
Ok(ws::Message::Binary(bin)) => {
|
||||
ctx.binary(bin);
|
||||
}
|
||||
Ok(ws::Message::Close(reason)) => {
|
||||
log::info(&format!("WS closed: {:?}", reason));
|
||||
ctx.close(reason);
|
||||
ctx.stop();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
pub async fn ws_route(
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
broker: web::Data<BrokerAddr>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let session = WsSession {
|
||||
broker: broker.get_ref().0.clone(),
|
||||
};
|
||||
let resp = ws::start(session, &req, stream);
|
||||
resp
|
||||
}
|
||||
Reference in new issue
Block a user