Merge pull request #1 from evandrodevbr/develop

feat: enhance chat functionality with markdown support and default prompts
This commit is contained in:
Evandro Fonseca Junior authored and GitHub committed 2025-11-19 00:40:26 -03:00
commit 7d169d8a61
266 files changed
+21098 -314

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+23 -314
View File
@@ -1,327 +1,36 @@
# OllaHub
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
Plataforma desktop multiplataforma para integração de modelos IA locais com servidores Model Context Protocol
## Getting Started
---
First, run the development server:
## Visão Geral
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
OllaHub é uma solução desktop nativa desenvolvida com Rust e Tauri que unifica o gerenciamento de Large Language Models locais através do Ollama com a extensibilidade do Model Context Protocol. A plataforma prioriza privacidade total, performance superior e experiência nativa, oferecendo uma alternativa local-first às soluções baseadas em nuvem.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
O projeto segue uma metodologia de desenvolvimento progressivo, iniciando com um MVP web para validação de conceitos antes da implementação completa da aplicação desktop.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
### Principais Características
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
**Inferência 100% Local**
Todo processamento de IA ocorre localmente através do Ollama, garantindo privacidade absoluta dos dados sem dependência de serviços externos.
## Learn More
**Integração Nativa com MCP**
Suporte completo ao Model Context Protocol permitindo extensão de capacidades através de servidores especializados para acesso a sistemas de arquivos, APIs externas, bancos de dados e ferramentas de desenvolvimento.
To learn more about Next.js, take a look at the following resources:
**Performance Excepcional**
Backend em Rust oferece velocidade de execução até 3x superior comparado a alternativas baseadas em Node.js ou Python, com consumo de memória reduzido em até 50%.
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
**Bundle Compacto**
Distribuição entre 3-5 MB comparado aos 85 MB típicos de aplicações Electron, facilitando download e instalação.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
**Experiência Desktop Nativa**
Interface responsiva construída com Tauri 2.0 e SvelteKit, priorizando Linux com suporte planejado para Windows.
## Deploy on Vercel
**Streaming em Tempo Real**
Sistema de streaming bidirecional para renderização progressiva de respostas longas, mantendo interface fluida durante processamento.
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
---
## Por Que OllaHub?
### Problemas Resolvidos
**Fragmentação de Ferramentas**
Elimina necessidade de múltiplas aplicações para gerenciar diferentes modelos IA e integrações contextuais.
**Dependência de Cloud**
Remove necessidade de conexão com serviços externos, permitindo uso completo offline após instalação inicial.
**Complexidade de Integração**
Abstrai complexidade do protocolo MCP oferecendo interface intuitiva para conexão com servidores especializados.
**Privacidade de Dados**
Garante que dados sensíveis nunca deixem a máquina do usuário durante interações com modelos.
### Casos de Uso
**Desenvolvimento Assistido por IA**
Integração com sistema de arquivos via MCP permite análise contextual de código-fonte, sugestões de refatoração e documentação automática.
**Análise Documental Local**
Implementação de RAG com documentos locais para pesquisa semântica, síntese de informações e geração de resumos.
**Automação de Workflows**
Criação de scripts e automações com acesso controlado a APIs externas através de servidores MCP especializados.
**Pesquisa e Agregação**
Combinação de múltiplas fontes de dados como repositórios GitHub, bancos de dados locais e sistemas de arquivos para análises complexas.
---
## Arquitetura
### Stack Tecnológico
#### Backend
- Rust: Linguagem core garantindo segurança de memória e performance
- Tauri 2.0: Framework desktop multiplataforma
- Axum: Framework web assíncrono para servidor REST interno
- Tokio: Runtime assíncrono de alta performance
- Reqwest: Cliente HTTP com suporte a streaming
- SQLx/Sled: Persistência de dados com queries type-safe
- Serde: Serialização JSON type-safe
#### Frontend
- SvelteKit 2.x: Meta-framework reativo com roteamento integrado
- TypeScript 5.x: Type-safety no frontend
- TailwindCSS: Framework CSS utility-first
- shadcn-svelte: Componentes UI acessíveis e customizáveis
- Marked: Renderização de Markdown para respostas formatadas
#### Infraestrutura
- Ollama: Gerenciamento de modelos LLM locais
- Model Context Protocol: Protocolo de extensibilidade via servidores especializados
- GitHub Actions: CI/CD para builds multiplataforma
### Componentes Principais
**Módulo de Gerenciamento Ollama**
Responsável por toda comunicação com a API REST do Ollama, incluindo listagem de modelos, inicialização de conversas, streaming de respostas e gerenciamento de contexto conversacional.
**Cliente MCP**
Implementa protocolo completo para descoberta, conexão e comunicação com servidores MCP via transports STDIO e HTTP+SSE, seguindo especificação JSON-RPC 2.0.
**Sistema de Persistência**
Gerencia armazenamento local de configurações, histórico de conversas, preferências do usuário e cache de dados utilizando banco embarcado para consultas eficientes.
**Engine de Streaming**
Implementa protocolo de streaming bidirecional permitindo renderização progressiva na interface durante geração de respostas longas.
**Camada de Segurança**
Gerencia autenticação de servidores MCP, validação de certificados, isolamento de processos e aplicação de políticas de segurança.
---
## Roadmap de Desenvolvimento
### Fase 1: Validação MVP Web (4-6 semanas)
**Objetivo**: Validar arquitetura técnica e fluxos de experiência do usuário antes de investimento no desenvolvimento Tauri completo.
**Sprint 1-2: Fundação Backend**
Inicialização de projeto Axum com estrutura modular, implementação de cliente Ollama com streaming via Server-Sent Events, desenvolvimento de endpoints REST para listagem de modelos e chat completion, criação de testes unitários.
**Sprint 3-4: Interface Web**
Setup de projeto SvelteKit com TypeScript, desenvolvimento de componente de chat com área de mensagens e seletor de modelos, implementação de sistema de histórico com persistência em LocalStorage, deploy de teste para validação externa.
**Sprint 5-6: Iteração e Refinamento**
Testes de usabilidade com desenvolvedores e entusiastas de IA, coleta de feedback qualitativo e quantitativo, otimização de performance do streaming, documentação de lições aprendidas para informar desenvolvimento Tauri.
### Fase 2: Aplicação Desktop Tauri (6-8 semanas)
**Sprint 7-9: Migração para Tauri**
Inicialização de projeto Tauri 2.0 com template SvelteKit, migração de lógica Axum para Tauri Commands, implementação de sistema IPC completo, desenvolvimento de sistema de eventos para comunicação backend-frontend.
**Sprint 10-12: Integração MCP**
Implementação completa de cliente MCP seguindo especificação oficial, sistema de descoberta automática de servidores MCP instalados, gerenciamento de múltiplas conexões simultâneas, interface de configuração para adicionar/remover servidores.
**Sprint 13-14: Features Avançadas**
Sistema de templates de prompt para casos de uso comuns, implementação de RAG básico com embeddings, configurações avançadas de modelos, sistema de plugins para extensibilidade, exportação de conversas em múltiplos formatos.
### Fase 3: Polish e Lançamento (3-4 semanas)
**Sprint 15-16: Qualidade e Performance**
Testes de carga e stress, profiling detalhado identificando gargalos, otimizações targeted de latências críticas, implementação de telemetria anonimizada, code review completo.
**Sprint 17-18: Documentação e Release**
Documentação completa de usuário com guias de início rápido, documentação técnica para desenvolvedores, vídeos tutoriais, setup de CI/CD para builds automáticos, lançamento em GitHub Releases e AUR para Arch Linux.
---
## Requisitos do Sistema
### Requisitos Mínimos
**Sistema Operacional**
Linux com kernel 5.10 ou superior (prioritário), Windows 10/11 com WSL2 (suporte planejado)
**Hardware**
Processador: 4 cores físicos, Memória RAM: 8 GB, Armazenamento: 10 GB disponíveis, GPU: Recomendado NVIDIA com CUDA ou AMD com ROCm para aceleração
**Dependências**
Ollama instalado e configurado, Node.js 20 LTS ou superior para desenvolvimento, Rust toolchain 1.75 ou superior para compilação
### Requisitos Recomendados
Processador: 8 cores físicos ou superior, Memória RAM: 16 GB ou superior, GPU: NVIDIA RTX 3060 ou superior com 12GB VRAM, Armazenamento: SSD NVMe para melhor performance
---
## Instalação
### Pré-requisitos
Antes de instalar o OllaHub, certifique-se que o Ollama está instalado e funcionando corretamente. Faça download do Ollama no site oficial e verifique a instalação executando o comando para listar modelos disponíveis.
### Instalação via Binário Pré-compilado
Faça download do binário apropriado para seu sistema operacional na seção Releases do repositório GitHub. Para Linux, escolha entre formato DEB para distribuições baseadas em Debian/Ubuntu, RPM para distribuições baseadas em Red Hat/Fedora, ou AppImage para distribuição universal.
Após download, instale o pacote conforme método de seu sistema operacional ou execute o AppImage diretamente após conceder permissões de execução.
### Compilação a partir do Código-fonte
Clone o repositório do GitHub, instale as dependências do frontend utilizando gerenciador de pacotes Node.js, compile o backend Rust, e execute o build final que gerará binário otimizado para produção.
---
## Uso
### Primeira Execução
Ao iniciar o OllaHub pela primeira vez, a aplicação realizará verificação de conexão com Ollama local. Caso não detecte instância em execução, será exibido alerta com instruções para iniciar o serviço.
Após conexão estabelecida, interface principal exibirá lista de modelos disponíveis baixados no Ollama. Selecione modelo desejado e inicie nova conversa.
### Gerenciamento de Modelos
A seção de modelos permite visualizar todos modelos disponíveis instalados localmente, incluindo informações sobre tamanho, capacidades e versão. Para adicionar novos modelos, utilize comandos do Ollama diretamente no terminal, e atualize a lista na interface do OllaHub.
### Integração com Servidores MCP
Acesse seção de configurações para adicionar servidores MCP. Para servidores locais, configure caminho do executável e argumentos necessários para inicialização via STDIO. Para servidores remotos, configure URL do endpoint HTTP e credenciais de autenticação se necessário.
Após configuração, OllaHub estabelecerá conexão e descobrirá automaticamente ferramentas e recursos disponíveis. Estas ferramentas aparecerão na interface e poderão ser invocadas durante conversas com modelos.
### Templates e Workflows
Utilize biblioteca de templates pré-configurados para casos de uso comuns como análise de código, revisão de documentos, geração de documentação técnica e debugging assistido. Templates podem ser customizados e salvos para reutilização futura.
### Exportação de Conversas
Conversas podem ser exportadas em formato Markdown para documentação, JSON para processamento programático, ou texto simples para compartilhamento. Acesse menu de contexto da conversa e selecione formato desejado.
---
## Segurança e Privacidade
### Processamento Local
Todo processamento de inferência ocorre localmente através do Ollama. Nenhum dado é transmitido para servidores externos durante uso normal da aplicação.
### Isolamento de Servidores MCP
Servidores MCP executam em processos separados com comunicação via pipes, prevenindo comprometimento do processo principal. Para servidores não-confiáveis, recomenda-se execução em containers Docker para camada adicional de isolamento.
### Persistência de Dados
Histórico de conversas e configurações são armazenados localmente em diretório de dados do usuário. Dados são salvos em formato SQLite ou JSON dependendo da configuração, permitindo backup e migração simples.
### Auditoria
Sistema de logging estruturado registra todas operações sensíveis incluindo conexões com servidores MCP, invocações de ferramentas externas e modificações de configuração. Logs podem ser revisados para auditoria de segurança.
---
## Contribuindo
OllaHub é um projeto open source e contribuições são bem-vindas. Antes de contribuir, leia as diretrizes de contribuição no arquivo CONTRIBUTING.md do repositório.
### Áreas de Contribuição
**Desenvolvimento de Features**
Implementação de novas funcionalidades seguindo roadmap do projeto ou propondo melhorias não planejadas.
**Correção de Bugs**
Identificação e correção de problemas reportados na seção Issues do GitHub.
**Documentação**
Melhoria de documentação de usuário, criação de tutoriais, tradução para outros idiomas.
**Testes**
Desenvolvimento de testes unitários e de integração, testes de usabilidade, identificação de casos extremos.
**Servidores MCP**
Desenvolvimento de servidores MCP especializados para integração com ferramentas e serviços específicos.
### Processo de Contribuição
Faça fork do repositório, crie branch para sua feature ou correção, implemente mudanças com commits descritivos, adicione testes quando apropriado, atualize documentação refletindo mudanças, envie pull request com descrição detalhada das alterações.
Todas contribuições serão revisadas pelos mantenedores do projeto antes de merge. Feedback construtivo será fornecido para auxiliar no processo de aprovação.
---
## Licença
Este projeto é licenciado sob a licença MIT. Veja arquivo LICENSE para detalhes completos.
A licença MIT permite uso comercial, modificação, distribuição e uso privado, desde que atribuição original seja mantida.
---
## Suporte e Comunidade
### Canais de Suporte
**GitHub Issues**
Para reportar bugs, solicitar features ou discutir aspectos técnicos do projeto.
**GitHub Discussions**
Para perguntas gerais, compartilhamento de casos de uso, discussões sobre arquitetura e decisões de design.
**Discord Community** (em planejamento)
Para suporte em tempo real, discussões da comunidade e compartilhamento de configurações e workflows.
### Recursos Adicionais
**Documentação Oficial**
Guias detalhados disponíveis no diretório docs do repositório cobrindo instalação, configuração, desenvolvimento e troubleshooting.
**Wiki do Projeto**
Artigos comunitários sobre casos de uso específicos, integrações com ferramentas populares e otimizações de performance.
**Exemplos de Código** (futuro)
Repositório de exemplos demonstrando integração do OllaHub com diferentes servidores MCP e casos de uso avançados.
---
## Reconhecimentos
Este projeto utiliza e é inspirado por diversos projetos open source extraordinários:
- Ollama pela plataforma de gerenciamento de modelos LLM locais
- Anthropic pelo desenvolvimento do Model Context Protocol
- Tauri pela framework de aplicações desktop com Rust
- SvelteKit pelo framework web reativo e performático
- Comunidade Rust pela linguagem e ecossistema de ferramentas excepcionais
Agradecimentos especiais aos early adopters e contribuidores que forneceram feedback valioso durante desenvolvimento do MVP.
---
## Status do Projeto
**Fase Atual**: MVP Web em desenvolvimento (Fase 1 - Sprint 3)
**Próximos Marcos**:
- Conclusão de testes de usabilidade do MVP web (2 semanas)
- Início da migração para Tauri (4 semanas)
- Implementação completa do cliente MCP (8 semanas)
- Lançamento da versão alpha para Linux (12 semanas)
Acompanhe progresso do desenvolvimento na seção Projects do GitHub e participe das discussões sobre features futuras.
---
**Desenvolvido com foco em privacidade, performance e experiência do usuário.**
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+211
View File
@@ -0,0 +1,211 @@
'use client';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { MessageSquare, Settings, Server, Moon, Sun, PanelLeftClose, PanelLeftOpen } from "lucide-react";
import { ChatInput } from "@/components/chat/chat-input";
import { ChatMessage } from "@/components/chat/chat-message";
import { SystemPanel } from "@/components/chat/system-panel";
import { PromptGeneratorDialog } from "@/components/chat/prompt-generator";
import { useChat } from "@/hooks/use-chat";
import { useLocalModels } from "@/hooks/use-local-models";
import { useState, useEffect, useRef } from "react";
import { useTheme } from "next-themes";
import { ImperativePanelHandle } from "react-resizable-panels";
// @ts-ignore
import defaultFormatPrompt from "@/data/prompts/default-format.md";
export default function ChatPage() {
const { messages, sendMessage, isLoading, stop } = useChat();
const { models } = useLocalModels();
const { theme, setTheme } = useTheme();
const [selectedModel, setSelectedModel] = useState("");
// Initialize with default format prompt
const [systemPrompt, setSystemPrompt] = useState(defaultFormatPrompt || "Você é um assistente útil e prestativo.");
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const sidebarRef = useRef<ImperativePanelHandle>(null);
// Auto-select first model
useEffect(() => {
if (models.length > 0 && !selectedModel) {
setSelectedModel(models[0].name);
}
}, [models, selectedModel]);
const handleSend = (content: string) => {
if (!selectedModel) return;
sendMessage(content, selectedModel, systemPrompt);
};
const toggleSidebar = () => {
const panel = sidebarRef.current;
if (panel) {
if (isSidebarCollapsed) {
panel.expand();
} else {
panel.collapse();
}
}
};
return (
<div className="h-screen w-full bg-background overflow-hidden flex">
{/* Left Sidebar (Nav) */}
<ResizablePanelGroup direction="horizontal" className="flex-1">
<ResizablePanel
ref={sidebarRef}
defaultSize={4}
minSize={4}
maxSize={15}
collapsible={true}
collapsedSize={0}
onCollapse={() => setIsSidebarCollapsed(true)}
onExpand={() => setIsSidebarCollapsed(false)}
className="border-r flex flex-col items-center py-4 gap-4 bg-muted/20 min-w-[60px]"
>
<Button variant="ghost" size="icon" className="rounded-lg bg-primary/10 text-primary">
<MessageSquare className="w-5 h-5" />
</Button>
<Button variant="ghost" size="icon" className="rounded-lg text-muted-foreground hover:text-foreground">
<Server className="w-5 h-5" />
</Button>
<div className="flex-1" />
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="rounded-lg text-muted-foreground hover:text-foreground"
>
{theme === "dark" ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
</Button>
<Button variant="ghost" size="icon" className="rounded-lg text-muted-foreground hover:text-foreground">
<Settings className="w-5 h-5" />
</Button>
</ResizablePanel>
<ResizableHandle />
{/* Main Chat Area */}
<ResizablePanel defaultSize={75} minSize={50}>
<div className="h-full flex flex-col">
{/* Header */}
<div className="h-14 border-b flex items-center px-4 justify-between bg-background/50 backdrop-blur gap-4">
<div className="flex items-center gap-2">
{isSidebarCollapsed && (
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="h-8 w-8">
<PanelLeftOpen className="w-4 h-4" />
</Button>
)}
{!isSidebarCollapsed && (
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="h-8 w-8 text-muted-foreground">
<PanelLeftClose className="w-4 h-4" />
</Button>
)}
<div className="font-semibold">Chat</div>
</div>
<div className="flex-1 max-w-[300px]">
<Select value={selectedModel} onValueChange={setSelectedModel}>
<SelectTrigger className="h-9">
<SelectValue placeholder="Selecione um modelo..." />
</SelectTrigger>
<SelectContent>
{models.map(m => (
<SelectItem key={m.name} value={m.name}>{m.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto scroll-smooth">
{messages.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center text-muted-foreground space-y-4">
<div className="p-4 rounded-full bg-muted/50">
<MessageSquare className="w-8 h-8" />
</div>
<p>Inicie uma conversa com {selectedModel}</p>
</div>
) : (
<div className="flex flex-col pb-4">
{messages.map((msg, i) => (
<ChatMessage key={i} message={msg} />
))}
{isLoading && (
<div className="px-6 py-4 text-xs text-muted-foreground animate-pulse">
Gerando resposta...
</div>
)}
</div>
)}
</div>
{/* Input */}
<ChatInput
onSend={handleSend}
onStop={stop}
isLoading={isLoading}
/>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Right Sidebar (Settings & Monitor) */}
<ResizablePanel defaultSize={25} minSize={20} maxSize={40} className="bg-muted/10">
<Tabs defaultValue="params" className="h-full flex flex-col">
<div className="border-b px-4">
<TabsList className="w-full justify-start h-12 bg-transparent p-0 gap-4">
<TabsTrigger
value="params"
className="data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none px-0"
>
Parâmetros
</TabsTrigger>
<TabsTrigger
value="system"
className="data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none px-0"
>
Sistema
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="params" className="flex-1 p-4 space-y-6 overflow-y-auto m-0">
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">System Prompt</label>
<PromptGeneratorDialog
defaultModel={selectedModel}
onPromptGenerated={setSystemPrompt}
/>
</div>
<Textarea
value={systemPrompt}
onChange={(e) => setSystemPrompt(e.target.value)}
className="min-h-[300px] resize-none font-mono text-sm"
placeholder="Defina como a IA deve se comportar..."
/>
<p className="text-xs text-muted-foreground">
Instruções globais para o comportamento do modelo.
</p>
</div>
</TabsContent>
<TabsContent value="system" className="flex-1 m-0 overflow-hidden">
<SystemPanel />
</TabsContent>
</Tabs>
</ResizablePanel>
</ResizablePanelGroup>
</div>
);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+122
View File
@@ -0,0 +1,122 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
+42
View File
@@ -0,0 +1,42 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "OllaHub",
description: "Interface moderna para Ollama",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="pt-BR" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
+66
View File
@@ -0,0 +1,66 @@
'use client';
import { useOllamaCheck } from "@/hooks/use-ollama-check";
import { Hero } from "@/components/landing/hero";
import { InstallModal } from "@/components/modals/install-modal";
import { StoppedCard } from "@/components/landing/stopped-card";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
export default function Home() {
const { status, check } = useOllamaCheck();
const router = useRouter();
const handleStart = () => {
const setupComplete = localStorage.getItem("ollahub_setup_complete");
if (setupComplete) {
router.push("/chat");
} else {
router.push("/setup");
}
};
if (status === 'checking') {
return (
<div className="flex h-screen w-full items-center justify-center bg-background">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<span className="ml-2 text-muted-foreground">Verificando sistema...</span>
</div>
);
}
return (
<main className="flex min-h-screen flex-col items-center justify-center p-4 bg-background text-foreground transition-colors duration-300">
{/* Layout for Running State */}
{status === 'running' && (
<Hero onStart={handleStart} />
)}
{/* Layout for Stopped State */}
{status === 'installed_stopped' && (
<div className="flex flex-col items-center gap-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold tracking-tighter">OllaHub</h1>
<p className="text-muted-foreground">Detectamos o Ollama, mas ele não está rodando.</p>
</div>
<StoppedCard onCheckAgain={check} />
</div>
)}
{/* Layout for Not Installed State (Background + Modal) */}
{status === 'not_installed' && (
<>
<div className="opacity-20 pointer-events-none blur-sm">
<Hero onStart={() => {}} />
</div>
{/* Hero is shown in background, modal is open */}
<InstallModal
open={true}
onCheckAgain={check}
/>
</>
)}
</main>
);
}
+41
View File
@@ -0,0 +1,41 @@
'use client';
import { useHardware } from "@/hooks/use-hardware";
import { HardwareScan } from "@/components/setup/hardware-scan";
import { ModelSelector } from "@/components/setup/model-selector";
import { getRecommendation } from "@/lib/recommendation";
import { useRouter } from "next/navigation";
export default function SetupPage() {
const { specs, loading } = useHardware();
const router = useRouter();
const handleComplete = () => {
// Save setup state if needed
localStorage.setItem("ollahub_setup_complete", "true");
router.push("/chat");
};
return (
<main className="flex min-h-screen flex-col items-center justify-center p-4 bg-background text-foreground">
<div className="w-full max-w-2xl space-y-8 animate-in fade-in duration-700">
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold tracking-tighter">Configuração Inicial</h1>
<p className="text-muted-foreground">Vamos preparar o ambiente ideal para você.</p>
</div>
<HardwareScan specs={specs} loading={loading} />
{!loading && specs && (
<div className="animate-in slide-in-from-bottom-8 duration-700 delay-300 fill-mode-backwards">
<ModelSelector
recommendation={getRecommendation(specs)}
onComplete={handleComplete}
/>
</div>
)}
</div>
</main>
);
}
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
+72
View File
@@ -0,0 +1,72 @@
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Send, Square } from "lucide-react";
import { useState, useRef, useEffect } from "react";
interface ChatInputProps {
onSend: (message: string) => void;
onStop: () => void;
isLoading: boolean;
}
export function ChatInput({ onSend, onStop, isLoading }: ChatInputProps) {
const [input, setInput] = useState("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
const handleSend = () => {
if (input.trim() && !isLoading) {
onSend(input);
setInput("");
}
};
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [input]);
return (
<div className="p-4 border-t bg-background">
<div className="relative flex items-end gap-2 max-w-3xl mx-auto">
<Textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Digite sua mensagem..."
className="min-h-[50px] max-h-[200px] resize-none pr-12 py-3"
rows={1}
/>
<div className="absolute right-2 bottom-2">
{isLoading ? (
<Button size="icon" variant="destructive" onClick={onStop} className="h-8 w-8">
<Square className="h-4 w-4 fill-current" />
</Button>
) : (
<Button
size="icon"
onClick={handleSend}
disabled={!input.trim()}
className="h-8 w-8"
>
<Send className="h-4 w-4" />
</Button>
)}
</div>
</div>
<div className="text-xs text-center text-muted-foreground mt-2">
OllaHub pode cometer erros. Verifique informações importantes.
</div>
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { cn } from "@/lib/utils";
import { Message } from "@/hooks/use-chat";
import { Bot, User } from "lucide-react";
import { markdownRenderers } from "./markdown-renderers";
import { MessageActions } from "./message-actions";
export function ChatMessage({ message }: { message: Message }) {
const isUser = message.role === 'user';
return (
<div className={cn(
"group flex w-full gap-4 p-6 relative",
isUser ? "bg-background" : "bg-muted/30"
)}>
<div className="flex-shrink-0 mt-1">
<div className={cn(
"w-8 h-8 rounded-sm flex items-center justify-center",
isUser ? "bg-primary text-primary-foreground" : "bg-secondary text-secondary-foreground"
)}>
{isUser ? <User className="w-5 h-5" /> : <Bot className="w-5 h-5" />}
</div>
</div>
<div className="flex-1 overflow-hidden min-w-0">
<div className="prose dark:prose-invert max-w-none break-words">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={markdownRenderers}
>
{message.content}
</ReactMarkdown>
</div>
</div>
{/* Actions Toolbar - Absolute positioned or flex end */}
{!isUser && (
<div className="absolute top-4 right-4">
<MessageActions content={message.content} role={message.role} />
</div>
)}
</div>
);
}
+45
View File
@@ -0,0 +1,45 @@
import { Check, Copy } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
interface CodeBlockProps {
language: string;
children: string;
}
export function CodeBlock({ language, children }: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="my-4 rounded-lg border bg-muted/50 overflow-hidden">
<div className="flex items-center justify-between px-4 py-2 bg-muted/80 border-b">
<span className="text-xs font-medium text-muted-foreground uppercase">
{language || "text"}
</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 hover:bg-background/50"
onClick={handleCopy}
title="Copiar código"
>
{copied ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : (
<Copy className="h-3.5 w-3.5 text-muted-foreground" />
)}
</Button>
</div>
<div className="p-4 overflow-x-auto">
<code className="text-sm font-mono whitespace-pre">{children}</code>
</div>
</div>
);
}
+138
View File
@@ -0,0 +1,138 @@
import { Components } from "react-markdown";
import { CodeBlock } from "./code-block";
import { cn } from "@/lib/utils";
export const markdownRenderers: Components = {
h1: ({ className, ...props }) => (
<h1
className={cn(
"mt-2 scroll-m-20 text-2xl font-bold tracking-tight first:mt-0",
className
)}
{...props}
/>
),
h2: ({ className, ...props }) => (
<h2
className={cn(
"mt-6 scroll-m-20 border-b pb-2 text-xl font-semibold tracking-tight first:mt-0",
className
)}
{...props}
/>
),
h3: ({ className, ...props }) => (
<h3
className={cn(
"mt-4 scroll-m-20 text-lg font-semibold tracking-tight",
className
)}
{...props}
/>
),
h4: ({ className, ...props }) => (
<h4
className={cn(
"mt-4 scroll-m-20 text-base font-semibold tracking-tight",
className
)}
{...props}
/>
),
p: ({ className, ...props }) => (
<p
className={cn("leading-7 [&:not(:first-child)]:mt-4", className)}
{...props}
/>
),
ul: ({ className, ...props }) => (
<ul className={cn("my-4 ml-6 list-disc [&>li]:mt-2", className)} {...props} />
),
ol: ({ className, ...props }) => (
<ol className={cn("my-4 ml-6 list-decimal [&>li]:mt-2", className)} {...props} />
),
li: ({ className, ...props }) => (
<li className={cn("mt-2", className)} {...props} />
),
blockquote: ({ className, ...props }) => (
<blockquote
className={cn(
"mt-4 border-l-2 border-primary pl-6 italic text-muted-foreground",
className
)}
{...props}
/>
),
img: ({ className, alt, ...props }) => (
// eslint-disable-next-line @next/next/no-img-element
<img
className={cn("rounded-md border bg-muted", className)}
alt={alt}
{...props}
/>
),
hr: ({ ...props }) => <hr className="my-4 md:my-6" {...props} />,
table: ({ className, ...props }) => (
<div className="my-4 w-full overflow-y-auto">
<table className={cn("w-full", className)} {...props} />
</div>
),
tr: ({ className, ...props }) => (
<tr
className={cn("m-0 border-t p-0 even:bg-muted", className)}
{...props}
/>
),
th: ({ className, ...props }) => (
<th
className={cn(
"border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
td: ({ className, ...props }) => (
<td
className={cn(
"border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
pre: ({ children }) => <>{children}</>,
code: ({ className, children, ...props }) => {
const match = /language-(\w+)/.exec(className || "");
const isInline = !match;
if (isInline) {
return (
<code
className={cn(
"relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold",
className
)}
{...props}
>
{children}
</code>
);
}
return (
<CodeBlock language={match[1]}>
{String(children).replace(/\n$/, "")}
</CodeBlock>
);
},
a: ({ className, ...props }) => (
<a
className={cn("font-medium underline underline-offset-4 text-primary hover:text-primary/80", className)}
target="_blank"
rel="noopener noreferrer"
{...props}
/>
),
};
+81
View File
@@ -0,0 +1,81 @@
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Copy, FileJson, FileText, MoreHorizontal } from "lucide-react";
import { useState } from "react";
interface MessageActionsProps {
content: string;
role: string;
}
export function MessageActions({ content, role }: MessageActionsProps) {
const [copied, setCopied] = useState(false);
const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const copyMarkdown = () => handleCopy(content);
const copyText = () => {
// Simple strip markdown (can be improved with a library if needed)
const text = content
.replace(/#{1,6}\s/g, '') // Headers
.replace(/(\*\*|__)(.*?)\1/g, '$2') // Bold
.replace(/(\*|_)(.*?)\1/g, '$2') // Italic
.replace(/`{3}[\s\S]*?`{3}/g, '$&') // Keep code blocks but maybe strip fences? For now keep raw
.replace(/`(.+?)`/g, '$1') // Inline code
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)') // Links
.replace(/>\s/g, ''); // Blockquotes
handleCopy(text);
};
const copyJson = () => {
const json = JSON.stringify({ role, content }, null, 2);
handleCopy(json);
};
return (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={copyMarkdown}
title="Copiar Markdown"
>
{copied ? (
<span className="text-green-500 text-xs font-bold">✓</span>
) : (
<Copy className="h-3.5 w-3.5 text-muted-foreground" />
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6">
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={copyText}>
<FileText className="mr-2 h-4 w-4" />
<span>Copiar Texto Puro</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={copyJson}>
<FileJson className="mr-2 h-4 w-4" />
<span>Copiar JSON</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
+163
View File
@@ -0,0 +1,163 @@
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Sparkles, Loader2 } from "lucide-react";
import { useState, useEffect } from "react";
import { usePromptGenerator } from "@/hooks/use-prompt-generator";
import { useLocalModels } from "@/hooks/use-local-models";
interface PromptGeneratorDialogProps {
defaultModel: string;
onPromptGenerated: (prompt: string) => void;
}
export function PromptGeneratorDialog({ defaultModel, onPromptGenerated }: PromptGeneratorDialogProps) {
const [open, setOpen] = useState(false);
const [goal, setGoal] = useState("");
const [selectedModel, setSelectedModel] = useState(defaultModel || "");
const { generatePrompt, isGenerating } = usePromptGenerator();
const { models, loading } = useLocalModels();
// Set default model when dialog opens or defaultModel changes
useEffect(() => {
if (defaultModel && defaultModel.trim()) {
setSelectedModel(defaultModel);
}
}, [defaultModel]);
// Try to set specific default if available and no model is selected
useEffect(() => {
if (loading || models.length === 0) return;
// Se já tem um modelo selecionado e válido, não alterar
if (selectedModel && models.some(m => m.name === selectedModel)) return;
// Primeiro, tenta usar o defaultModel se for válido
if (defaultModel && defaultModel.trim() && models.some(m => m.name === defaultModel)) {
setSelectedModel(defaultModel);
return;
}
// Fallback para modelo preferido
const preferredModel = "llama3.2-abliterate:3b";
const hasPreferred = models.some(m => m.name === preferredModel);
if (hasPreferred && !selectedModel.trim()) {
setSelectedModel(preferredModel);
return;
}
// Se nenhum modelo está selecionado, seleciona o primeiro disponível
if (!selectedModel.trim() && models.length > 0) {
setSelectedModel(models[0].name);
}
}, [models, loading, defaultModel, selectedModel]);
const handleGenerate = async () => {
if (!goal.trim() || !selectedModel.trim()) return;
try {
const prompt = await generatePrompt(goal, selectedModel);
onPromptGenerated(prompt);
setOpen(false);
setGoal("");
} catch (error) {
console.error("Erro ao gerar prompt:", error);
// TODO: Implementar toast de erro quando disponível
}
};
const handleCancel = () => {
setOpen(false);
setGoal("");
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6 text-yellow-500 hover:text-yellow-600 hover:bg-yellow-100 dark:hover:bg-yellow-900/20" title="Gerar com IA">
<Sparkles className="w-4 h-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Gerador de Prompts</DialogTitle>
<DialogDescription>
Descreva o que você quer que a IA faça, e nós criaremos um System Prompt otimizado para você.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label htmlFor="model-select" className="text-sm font-medium">
Modelo para Geração
</label>
<Select
value={selectedModel || undefined}
onValueChange={setSelectedModel}
disabled={loading || models.length === 0}
>
<SelectTrigger id="model-select">
<SelectValue placeholder={
loading
? "Carregando modelos..."
: models.length === 0
? "Nenhum modelo disponível"
: "Selecione um modelo..."
} />
</SelectTrigger>
<SelectContent>
{models.length === 0 ? (
<SelectItem value="" disabled>Nenhum modelo disponível</SelectItem>
) : (
models.map(m => (
<SelectItem key={m.name} value={m.name}>{m.name}</SelectItem>
))
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
Recomendado: Modelos com boa capacidade de instrução (ex: Llama 3, Mistral).
</p>
</div>
<div className="space-y-2">
<label htmlFor="goal-textarea" className="text-sm font-medium">
Seu Objetivo
</label>
<Textarea
id="goal-textarea"
value={goal}
onChange={(e) => setGoal(e.target.value)}
placeholder="Ex: Quero um assistente especialista em Python que explique conceitos complexos de forma simples..."
className="min-h-[150px]"
disabled={isGenerating}
/>
</div>
</div>
<DialogFooter>
<Button variant="ghost" onClick={handleCancel} disabled={isGenerating}>
Cancelar
</Button>
<Button
onClick={handleGenerate}
disabled={isGenerating || !goal.trim() || !selectedModel.trim() || models.length === 0}
>
{isGenerating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Gerando...
</>
) : (
<>
<Sparkles className="mr-2 h-4 w-4" />
Gerar Prompt
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+105
View File
@@ -0,0 +1,105 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Button } from "@/components/ui/button";
import { Trash2, HardDrive, Cpu, Database } from "lucide-react";
import { useSystemMonitor } from "@/hooks/use-system-monitor";
import { useLocalModels } from "@/hooks/use-local-models";
export function SystemPanel() {
const stats = useSystemMonitor();
const { models, deleteModel, refresh } = useLocalModels();
const ramUsagePercent = stats
? (stats.memory_used / stats.memory_total) * 100
: 0;
const formatBytes = (bytes: number) => {
const gb = bytes / (1024 * 1024 * 1024);
return `${gb.toFixed(1)} GB`;
};
return (
<div className="h-full overflow-y-auto p-4 space-y-6">
{/* Hardware Stats */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Monitoramento
</h3>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<Cpu className="w-4 h-4" /> Processador
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-between text-xs mb-1">
<span>Uso</span>
<span>{stats?.cpu_usage.toFixed(1)}%</span>
</div>
<Progress value={stats?.cpu_usage || 0} className="h-2" />
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<HardDrive className="w-4 h-4" /> Memória RAM
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-between text-xs mb-1">
<span>{stats ? formatBytes(stats.memory_used) : '-'}</span>
<span>{stats ? formatBytes(stats.memory_total) : '-'}</span>
</div>
<Progress value={ramUsagePercent} className="h-2" />
</CardContent>
</Card>
</div>
{/* Models List */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Modelos Instalados
</h3>
<Button variant="ghost" size="sm" onClick={refresh} className="h-6 text-xs">
Atualizar
</Button>
</div>
<div className="space-y-2">
{models.map((model) => (
<Card key={model.id} className="overflow-hidden">
<CardContent className="p-3 flex items-center justify-between">
<div className="min-w-0">
<div className="font-medium text-sm truncate" title={model.name}>
{model.name}
</div>
<div className="text-xs text-muted-foreground flex items-center gap-2">
<Database className="w-3 h-3" />
{model.size}
</div>
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive hover:text-destructive hover:bg-destructive/10"
onClick={() => deleteModel(model.name)}
>
<Trash2 className="w-4 h-4" />
</Button>
</CardContent>
</Card>
))}
{models.length === 0 && (
<div className="text-center text-sm text-muted-foreground py-4">
Nenhum modelo encontrado.
</div>
)}
</div>
</div>
</div>
);
}
+26
View File
@@ -0,0 +1,26 @@
import { Button } from "@/components/ui/button";
import { ArrowRight, Cpu } from "lucide-react";
export function Hero({ onStart }: { onStart: () => void }) {
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4 space-y-6 animate-in fade-in zoom-in duration-500">
<div className="p-4 rounded-full bg-primary/10 animate-pulse">
<Cpu className="w-12 h-12 text-primary" />
</div>
<h1 className="text-4xl md:text-6xl font-bold tracking-tighter bg-clip-text text-transparent bg-gradient-to-r from-primary to-primary/60">
OllaHub
</h1>
<p className="text-lg md:text-xl text-muted-foreground max-w-[600px]">
Sua interface moderna e poderosa para interagir com Ollama e Modelos de Linguagem Locais.
</p>
<div className="flex gap-4 pt-4">
<Button size="lg" onClick={onStart} className="group text-lg px-8 h-12">
Iniciar Chat
<ArrowRight className="ml-2 w-5 h-5 transition-transform group-hover:translate-x-1" />
</Button>
</div>
</div>
);
}
+85
View File
@@ -0,0 +1,85 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { RefreshCw, Copy, Check, Play, Loader2 } from "lucide-react"
import { useState } from "react"
import { invoke } from "@tauri-apps/api/core"
export function StoppedCard({ onCheckAgain }: { onCheckAgain: () => void }) {
const [copied, setCopied] = useState(false);
const [starting, setStarting] = useState(false);
const command = "ollama serve";
const handleCopy = () => {
navigator.clipboard.writeText(command);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleStart = async () => {
setStarting(true);
try {
await invoke('start_ollama_server');
// Wait a bit for the server to start before checking again
setTimeout(() => {
onCheckAgain();
setStarting(false);
}, 3000);
} catch (error) {
console.error("Failed to start ollama:", error);
setStarting(false);
}
};
return (
<Card className="w-full max-w-md mx-auto border-yellow-500/50 bg-yellow-50/10 dark:bg-yellow-900/10">
<CardHeader>
<CardTitle className="text-yellow-600 dark:text-yellow-500">Ollama está parado</CardTitle>
<CardDescription>
O Ollama está instalado mas não está rodando. Você pode iniciá-lo automaticamente ou executar o comando manualmente.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button
onClick={handleStart}
className="w-full bg-yellow-600 hover:bg-yellow-700 text-white"
disabled={starting}
>
{starting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Iniciando...
</>
) : (
<>
<Play className="mr-2 h-4 w-4" />
Iniciar Ollama Automaticamente
</>
)}
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-yellow-500/20" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">Ou manualmente</span>
</div>
</div>
<div className="flex items-center space-x-2">
<div className="bg-muted p-3 rounded-md font-mono text-sm flex-1 border">
{command}
</div>
<Button size="icon" variant="outline" onClick={handleCopy}>
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
<Button onClick={onCheckAgain} className="w-full" variant="secondary">
<RefreshCw className="mr-2 h-4 w-4" />
Verificar Novamente
</Button>
</CardContent>
</Card>
)
}
+86
View File
@@ -0,0 +1,86 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Button } from "@/components/ui/button"
import { Download } from "lucide-react"
interface InstallModalProps {
open: boolean;
onCheckAgain: () => void;
}
export function InstallModal({ open, onCheckAgain }: InstallModalProps) {
return (
<Dialog open={open}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Ollama não encontrado</DialogTitle>
<DialogDescription>
Para usar o OllaHub, você precisa ter o Ollama instalado no seu sistema.
Siga os passos abaixo para o seu sistema operacional.
</DialogDescription>
</DialogHeader>
<Tabs defaultValue="mac" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="mac">macOS</TabsTrigger>
<TabsTrigger value="linux">Linux</TabsTrigger>
<TabsTrigger value="windows">Windows</TabsTrigger>
</TabsList>
<TabsContent value="mac" className="space-y-4 py-4">
<div className="space-y-2">
<h3 className="font-medium">1. Download e Instalação</h3>
<Button variant="outline" className="w-full" asChild>
<a href="https://ollama.com/download/Ollama-darwin.zip" target="_blank" rel="noopener noreferrer">
<Download className="mr-2 h-4 w-4" />
Baixar para macOS
</a>
</Button>
</div>
<div className="text-sm text-muted-foreground">
Após instalar, abra o terminal e digite <code>ollama serve</code> se necessário.
</div>
</TabsContent>
<TabsContent value="linux" className="space-y-4 py-4">
<div className="space-y-2">
<h3 className="font-medium">Instalação via Terminal</h3>
<div className="bg-muted p-4 rounded-md relative group">
<code className="text-sm break-all">curl -fsSL https://ollama.com/install.sh | sh</code>
</div>
</div>
<div className="space-y-2">
<h3 className="font-medium">Comando Manual</h3>
<div className="text-sm text-muted-foreground">
Ou consulte <a href="https://ollama.com/download/linux" className="underline text-primary" target="_blank" rel="noopener noreferrer">ollama.com/download/linux</a> para instruções manuais.
</div>
</div>
</TabsContent>
<TabsContent value="windows" className="space-y-4 py-4">
<div className="space-y-2">
<h3 className="font-medium">1. Download do Instalador</h3>
<Button variant="outline" className="w-full" asChild>
<a href="https://ollama.com/download/OllamaSetup.exe" target="_blank" rel="noopener noreferrer">
<Download className="mr-2 h-4 w-4" />
Baixar para Windows
</a>
</Button>
</div>
<div className="text-sm text-muted-foreground">
Execute o instalador baixado e siga as instruções na tela.
</div>
</TabsContent>
</Tabs>
<div className="flex justify-end gap-2 mt-4">
<Button onClick={onCheckAgain}>
Verificar Novamente
</Button>
</div>
</DialogContent>
</Dialog>
)
}
+72
View File
@@ -0,0 +1,72 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Cpu, HardDrive, Laptop } from "lucide-react";
import { SystemSpecs } from "@/lib/recommendation";
import { useEffect, useState } from "react";
interface HardwareScanProps {
specs: SystemSpecs | null;
loading: boolean;
}
export function HardwareScan({ specs, loading }: HardwareScanProps) {
const [progress, setProgress] = useState(0);
useEffect(() => {
if (loading) {
const interval = setInterval(() => {
setProgress((prev) => (prev >= 90 ? 90 : prev + 10));
}, 150);
return () => clearInterval(interval);
} else {
setProgress(100);
}
}, [loading]);
if (loading) {
return (
<Card className="w-full max-w-md mx-auto border-primary/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Cpu className="animate-pulse text-primary" />
Analisando Hardware...
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Progress value={progress} className="h-2" />
<p className="text-sm text-muted-foreground text-center">
Verificando memória e processador para recomendação ideal.
</p>
</CardContent>
</Card>
);
}
if (!specs) return null;
const ramGB = (specs.total_memory / (1024 * 1024 * 1024)).toFixed(1);
return (
<Card className="w-full max-w-md mx-auto bg-muted/30 animate-in fade-in slide-in-from-bottom-4">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Laptop className="text-primary" />
Sistema Detectado
</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-4">
<div className="flex flex-col items-center p-3 bg-background rounded-lg border">
<HardDrive className="w-6 h-6 mb-2 text-muted-foreground" />
<span className="text-xl font-bold">{ramGB} GB</span>
<span className="text-xs text-muted-foreground">RAM Total</span>
</div>
<div className="flex flex-col items-center p-3 bg-background rounded-lg border">
<Cpu className="w-6 h-6 mb-2 text-muted-foreground" />
<span className="text-xl font-bold">{specs.cpu_count}</span>
<span className="text-xs text-muted-foreground">Núcleos CPU</span>
</div>
</CardContent>
</Card>
);
}
+163
View File
@@ -0,0 +1,163 @@
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Progress } from "@/components/ui/progress";
import { Badge } from "@/components/ui/badge";
import { Check, Download, Loader2, Play } from "lucide-react";
import { RECOMMENDED_MODELS, ModelRecommendation } from "@/lib/recommendation";
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
interface ModelSelectorProps {
recommendation: ModelRecommendation;
onComplete: () => void;
}
export function ModelSelector({ recommendation, onComplete }: ModelSelectorProps) {
const [selectedModelId, setSelectedModelId] = useState(recommendation.modelId);
const [isInstalled, setIsInstalled] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [downloadStatus, setDownloadStatus] = useState("");
const selectedModel = RECOMMENDED_MODELS.find(m => m.id === selectedModelId) || RECOMMENDED_MODELS[0];
useEffect(() => {
checkInstallation(selectedModelId);
}, [selectedModelId]);
useEffect(() => {
const unlisten = listen<string>('download-progress', (event) => {
const line = event.payload;
setDownloadStatus(line);
// Simple heuristic parsing for progress
// Ollama output example: "pulling manifest", "downloading 10%", "verifying sha256 digest"
if (line.includes('%')) {
const match = line.match(/(\d+)%/);
if (match) {
setDownloadProgress(parseInt(match[1], 10));
}
} else if (line.includes('success')) {
setDownloadProgress(100);
}
});
return () => {
unlisten.then(f => f());
};
}, []);
const checkInstallation = async (modelId: string) => {
try {
const installed = await invoke<boolean>('check_if_model_installed', { name: modelId });
setIsInstalled(installed);
} catch (e) {
console.error("Check failed", e);
}
};
const handleDownload = async () => {
setIsDownloading(true);
setDownloadProgress(0);
try {
await invoke('pull_model', { name: selectedModelId });
setIsInstalled(true);
setIsDownloading(false);
} catch (error) {
console.error("Download failed", error);
setDownloadStatus("Falha no download. Tente novamente.");
setIsDownloading(false);
}
};
return (
<Card className="w-full max-w-md mx-auto mt-6 border-primary shadow-lg">
<CardHeader>
<div className="flex justify-between items-start">
<div>
<CardTitle>Escolha seu Modelo</CardTitle>
<CardDescription>
{selectedModelId === recommendation.modelId
? "Baseado no seu hardware, recomendamos:"
: "Você selecionou um modelo personalizado:"}
</CardDescription>
</div>
{selectedModelId === recommendation.modelId && (
<Badge variant="secondary" className="bg-green-100 text-green-800 hover:bg-green-100 dark:bg-green-900 dark:text-green-100">
Recomendado
</Badge>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
<Select
value={selectedModelId}
onValueChange={setSelectedModelId}
disabled={isDownloading}
>
<SelectTrigger className="w-full h-14 text-lg">
<SelectValue placeholder="Selecione um modelo" />
</SelectTrigger>
<SelectContent>
{RECOMMENDED_MODELS.map((model) => (
<SelectItem key={model.id} value={model.id}>
<div className="flex flex-col items-start">
<span className="font-medium">{model.name}</span>
<span className="text-xs text-muted-foreground">{model.size} • Min RAM: {model.minRam}GB</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<div className="bg-muted/50 p-4 rounded-md text-sm">
<p className="font-medium mb-1">Sobre este modelo:</p>
<p className="text-muted-foreground">
{selectedModelId === recommendation.modelId
? recommendation.reason
: "Modelo alternativo selecionado. Certifique-se de ter memória suficiente."}
</p>
</div>
{isDownloading && (
<div className="space-y-2">
<div className="flex justify-between text-xs text-muted-foreground">
<span>{downloadStatus || "Iniciando..."}</span>
<span>{downloadProgress}%</span>
</div>
<Progress value={downloadProgress} className="h-2" />
</div>
)}
</CardContent>
<CardFooter>
{isInstalled ? (
<Button className="w-full h-12 text-lg" onClick={onComplete}>
Ir para o Chat
<Play className="ml-2 w-5 h-5" />
</Button>
) : (
<Button
className="w-full h-12 text-lg"
onClick={handleDownload}
disabled={isDownloading}
>
{isDownloading ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Baixando...
</>
) : (
<>
<Download className="mr-2 h-5 w-5" />
Baixar e Instalar ({selectedModel.size})
</>
)}
</Button>
)}
</CardFooter>
</Card>
);
}
+12
View File
@@ -0,0 +1,12 @@
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
+46
View File
@@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }
+60
View File
@@ -0,0 +1,60 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
+92
View File
@@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
+143
View File
@@ -0,0 +1,143 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
+199
View File
@@ -0,0 +1,199 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
+27
View File
@@ -0,0 +1,27 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }
+44
View File
@@ -0,0 +1,44 @@
import * as React from "react"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
const ResizablePanelGroup = ({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<ResizablePrimitive.PanelGroup
className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className
)}
{...props}
/>
)
const ResizablePanel = ResizablePrimitive.Panel
const ResizableHandle = ({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean
}) => (
<ResizablePrimitive.PanelResizeHandle
className={cn(
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical className="h-2.5 w-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
)
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
+159
View File
@@ -0,0 +1,159 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
+28
View File
@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }
+66
View File
@@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
+23
View File
@@ -0,0 +1,23 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }
+20
View File
@@ -0,0 +1,20 @@
{
"model": "alfred",
"url": "https://ollama.com/library/alfred",
"description": "A robust conversational model designed to be used for both chat and instruct use cases.",
"variantsCount": 2,
"variants": [
{
"name": "alfred:latest",
"size": "24GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "alfred:40b",
"size": "24GB",
"context": "2K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "all-minilm",
"url": "https://ollama.com/library/all-minilm",
"description": "Embedding models on very large sentence level datasets.",
"variantsCount": 3,
"variants": [
{
"name": "all-minilm:latest",
"size": "46MB",
"context": "512",
"inputType": "Text"
},
{
"name": "all-minilm:22m",
"size": "46MB",
"context": "512",
"inputType": "Text"
},
{
"name": "all-minilm:33m",
"size": "67MB",
"context": "512",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "athene-v2",
"url": "https://ollama.com/library/athene-v2",
"description": "Athene-V2 is a 72B parameter model which excels at code completion, mathematics, and log extraction tasks.",
"variantsCount": 2,
"variants": [
{
"name": "athene-v2:latest",
"size": "47GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "athene-v2:72b",
"size": "47GB",
"context": "32K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "aya-expanse",
"url": "https://ollama.com/library/aya-expanse",
"description": "Cohere For AI's language models trained to perform well across 23 different languages.",
"variantsCount": 3,
"variants": [
{
"name": "aya-expanse:latest",
"size": "5.1GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "aya-expanse:8b",
"size": "5.1GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "aya-expanse:32b",
"size": "20GB",
"context": "8K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "aya",
"url": "https://ollama.com/library/aya",
"description": "Aya 23, released by Cohere, is a new family of state-of-the-art, multilingual models that support 23 languages. ",
"variantsCount": 3,
"variants": [
{
"name": "aya:latest",
"size": "4.8GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "aya:8b",
"size": "4.8GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "aya:35b",
"size": "20GB",
"context": "8K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "bakllava",
"url": "https://ollama.com/library/bakllava",
"description": "BakLLaVA is a multimodal model consisting of the Mistral 7B base model augmented with the LLaVA architecture.",
"variantsCount": 2,
"variants": [
{
"name": "bakllava:latest",
"size": "4.7GB",
"context": "32K",
"inputType": "Text, Image"
},
{
"name": "bakllava:7b",
"size": "4.7GB",
"context": "32K",
"inputType": "Text, Image"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "bespoke-minicheck",
"url": "https://ollama.com/library/bespoke-minicheck",
"description": "A state-of-the-art fact-checking model developed by Bespoke Labs.",
"variantsCount": 2,
"variants": [
{
"name": "bespoke-minicheck:latest",
"size": "4.7GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "bespoke-minicheck:7b",
"size": "4.7GB",
"context": "32K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "bge-large",
"url": "https://ollama.com/library/bge-large",
"description": "Embedding model from BAAI mapping texts to vectors.",
"variantsCount": 2,
"variants": [
{
"name": "bge-large:latest",
"size": "671MB",
"context": "512",
"inputType": "Text"
},
{
"name": "bge-large:335m",
"size": "671MB",
"context": "512",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "bge-m3",
"url": "https://ollama.com/library/bge-m3",
"description": "BGE-M3 is a new model from BAAI distinguished for its versatility in Multi-Functionality, Multi-Linguality, and Multi-Granularity.",
"variantsCount": 2,
"variants": [
{
"name": "bge-m3:latest",
"size": "1.2GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "bge-m3:567m",
"size": "1.2GB",
"context": "8K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "codebooga",
"url": "https://ollama.com/library/codebooga",
"description": "A high-performing code instruct model created by merging two existing code models.",
"variantsCount": 2,
"variants": [
{
"name": "codebooga:latest",
"size": "19GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "codebooga:34b",
"size": "19GB",
"context": "16K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "codegeex4",
"url": "https://ollama.com/library/codegeex4",
"description": "A versatile model for AI software development scenarios, including code completion.",
"variantsCount": 2,
"variants": [
{
"name": "codegeex4:latest",
"size": "5.5GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "codegeex4:9b",
"size": "5.5GB",
"context": "128K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "codegemma",
"url": "https://ollama.com/library/codegemma",
"description": "CodeGemma is a collection of powerful, lightweight models that can perform a variety of coding tasks like fill-in-the-middle code completion, code generation, natural language understanding, mathematical reasoning, and instruction following.",
"variantsCount": 3,
"variants": [
{
"name": "codegemma:latest",
"size": "5.0GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "codegemma:2b",
"size": "1.6GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "codegemma:7b",
"size": "5.0GB",
"context": "8K",
"inputType": "Text"
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"model": "codellama",
"url": "https://ollama.com/library/codellama",
"description": "A large language model that can use text prompts to generate and discuss code.",
"variantsCount": 5,
"variants": [
{
"name": "codellama:latest",
"size": "3.8GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "codellama:7b",
"size": "3.8GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "codellama:13b",
"size": "7.4GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "codellama:34b",
"size": "19GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "codellama:70b",
"size": "39GB",
"context": "2K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "codeqwen",
"url": "https://ollama.com/library/codeqwen",
"description": "CodeQwen1.5 is a large language model pretrained on a large amount of code data.",
"variantsCount": 2,
"variants": [
{
"name": "codeqwen:latest",
"size": "4.2GB",
"context": "64K",
"inputType": "Text"
},
{
"name": "codeqwen:7b",
"size": "4.2GB",
"context": "64K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "codestral",
"url": "https://ollama.com/library/codestral",
"description": "Codestral is Mistral AI’s first-ever code model designed for code generation tasks.",
"variantsCount": 2,
"variants": [
{
"name": "codestral:latest",
"size": "13GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "codestral:22b",
"size": "13GB",
"context": "32K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "codeup",
"url": "https://ollama.com/library/codeup",
"description": "Great code generation model based on Llama2.",
"variantsCount": 2,
"variants": [
{
"name": "codeup:latest",
"size": "7.4GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "codeup:13b",
"size": "7.4GB",
"context": "4K",
"inputType": "Text"
}
]
}
+44
View File
@@ -0,0 +1,44 @@
{
"model": "cogito",
"url": "https://ollama.com/library/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, including counterparts from LLaMA, DeepSeek, and Qwen across most standard benchmarks.",
"variantsCount": 6,
"variants": [
{
"name": "cogito:latest",
"size": "4.9GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "cogito:3b",
"size": "2.2GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "cogito:8b",
"size": "4.9GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "cogito:14b",
"size": "9.0GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "cogito:32b",
"size": "20GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "cogito:70b",
"size": "43GB",
"context": "128K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "command-a",
"url": "https://ollama.com/library/command-a",
"description": "111 billion parameter model optimized for demanding enterprises that require fast, secure, and high-quality AI",
"variantsCount": 2,
"variants": [
{
"name": "command-a:latest",
"size": "67GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "command-a:111b",
"size": "67GB",
"context": "16K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "command-r-plus",
"url": "https://ollama.com/library/command-r-plus",
"description": "Command R+ is a powerful, scalable large language model purpose-built to excel at real-world enterprise use cases.",
"variantsCount": 2,
"variants": [
{
"name": "command-r-plus:latest",
"size": "59GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "command-r-plus:104b",
"size": "59GB",
"context": "128K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "command-r",
"url": "https://ollama.com/library/command-r",
"description": "Command R is a Large Language Model optimized for conversational interaction and long context tasks.",
"variantsCount": 2,
"variants": [
{
"name": "command-r:latest",
"size": "19GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "command-r:35b",
"size": "19GB",
"context": "128K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "command-r7b-arabic",
"url": "https://ollama.com/library/command-r7b-arabic",
"description": "A new state-of-the-art version of the lightweight Command R7B model that excels in advanced Arabic language capabilities for enterprises in the Middle East and Northern Africa.",
"variantsCount": 2,
"variants": [
{
"name": "command-r7b-arabic:latest",
"size": "5.1GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "command-r7b-arabic:7b",
"size": "5.1GB",
"context": "16K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "command-r7b",
"url": "https://ollama.com/library/command-r7b",
"description": "The smallest model in Cohere's R series delivers top-tier speed, efficiency, and quality to build powerful AI applications on commodity GPUs and edge devices.",
"variantsCount": 2,
"variants": [
{
"name": "command-r7b:latest",
"size": "5.1GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "command-r7b:7b",
"size": "5.1GB",
"context": "8K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "dbrx",
"url": "https://ollama.com/library/dbrx",
"description": "DBRX is an open, general-purpose LLM created by Databricks.",
"variantsCount": 2,
"variants": [
{
"name": "dbrx:latest",
"size": "74GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "dbrx:132b",
"size": "74GB",
"context": "32K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "deepcoder",
"url": "https://ollama.com/library/deepcoder",
"description": "DeepCoder is a fully open-Source 14B coder model at O3-mini level, with a 1.5B version also available.",
"variantsCount": 3,
"variants": [
{
"name": "deepcoder:latest",
"size": "9.0GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepcoder:1.5b",
"size": "1.1GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepcoder:14b",
"size": "9.0GB",
"context": "128K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "deepscaler",
"url": "https://ollama.com/library/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 on popular math evaluations.",
"variantsCount": 2,
"variants": [
{
"name": "deepscaler:latest",
"size": "3.6GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepscaler:1.5b",
"size": "3.6GB",
"context": "128K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "deepseek-coder-v2",
"url": "https://ollama.com/library/deepseek-coder-v2",
"description": "An open-source Mixture-of-Experts code language model that achieves performance comparable to GPT4-Turbo in code-specific tasks.",
"variantsCount": 3,
"variants": [
{
"name": "deepseek-coder-v2:latest",
"size": "8.9GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-coder-v2:16b",
"size": "8.9GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-coder-v2:236b",
"size": "133GB",
"context": "4K",
"inputType": "Text"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"model": "deepseek-coder",
"url": "https://ollama.com/library/deepseek-coder",
"description": "DeepSeek Coder is a capable coding model trained on two trillion code and natural language tokens.",
"variantsCount": 4,
"variants": [
{
"name": "deepseek-coder:latest",
"size": "776MB",
"context": "16K",
"inputType": "Text"
},
{
"name": "deepseek-coder:1.3b",
"size": "776MB",
"context": "16K",
"inputType": "Text"
},
{
"name": "deepseek-coder:6.7b",
"size": "3.8GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "deepseek-coder:33b",
"size": "19GB",
"context": "16K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "deepseek-llm",
"url": "https://ollama.com/library/deepseek-llm",
"description": "An advanced language model crafted with 2 trillion bilingual tokens.",
"variantsCount": 3,
"variants": [
{
"name": "deepseek-llm:latest",
"size": "4.0GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "deepseek-llm:7b",
"size": "4.0GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "deepseek-llm:67b",
"size": "38GB",
"context": "4K",
"inputType": "Text"
}
]
}
+56
View File
@@ -0,0 +1,56 @@
{
"model": "deepseek-r1",
"url": "https://ollama.com/library/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.",
"variantsCount": 8,
"variants": [
{
"name": "deepseek-r1:latest",
"size": "5.2GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:1.5b",
"size": "1.1GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:7b",
"size": "4.7GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:8b",
"size": "5.2GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:14b",
"size": "9.0GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:32b",
"size": "20GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:70b",
"size": "43GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "deepseek-r1:671b",
"size": "404GB",
"context": "160K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "deepseek-v2.5",
"url": "https://ollama.com/library/deepseek-v2.5",
"description": "An upgraded version of DeekSeek-V2 that integrates the general and coding abilities of both DeepSeek-V2-Chat and DeepSeek-Coder-V2-Instruct.",
"variantsCount": 2,
"variants": [
{
"name": "deepseek-v2.5:latest",
"size": "133GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "deepseek-v2.5:236b",
"size": "133GB",
"context": "4K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "deepseek-v2",
"url": "https://ollama.com/library/deepseek-v2",
"description": "A strong, economical, and efficient Mixture-of-Experts language model.",
"variantsCount": 3,
"variants": [
{
"name": "deepseek-v2:latest",
"size": "8.9GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-v2:16b",
"size": "8.9GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-v2:236b",
"size": "133GB",
"context": "4K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "deepseek-v3.1",
"url": "https://ollama.com/library/deepseek-v3.1",
"description": "DeepSeek-V3.1-Terminus is a hybrid model that supports both thinking mode and non-thinking mode.",
"variantsCount": 3,
"variants": [
{
"name": "deepseek-v3.1:latest",
"size": "404GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-v3.1:671b",
"size": "404GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-v3.1:671b-cloud",
"size": "-",
"context": "160K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "deepseek-v3",
"url": "https://ollama.com/library/deepseek-v3",
"description": "A strong Mixture-of-Experts (MoE) language model with 671B total parameters with 37B activated for each token.",
"variantsCount": 2,
"variants": [
{
"name": "deepseek-v3:latest",
"size": "404GB",
"context": "160K",
"inputType": "Text"
},
{
"name": "deepseek-v3:671b",
"size": "404GB",
"context": "160K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "devstral",
"url": "https://ollama.com/library/devstral",
"description": "Devstral: the best open source model for coding agents",
"variantsCount": 2,
"variants": [
{
"name": "devstral:latest",
"size": "14GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "devstral:24b",
"size": "14GB",
"context": "128K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "dolphin-llama3",
"url": "https://ollama.com/library/dolphin-llama3",
"description": "Dolphin 2.9 is a new model with 8B and 70B sizes by Eric Hartford based on Llama 3 that has a variety of instruction, conversational, and coding skills.",
"variantsCount": 3,
"variants": [
{
"name": "dolphin-llama3:latest",
"size": "4.7GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "dolphin-llama3:8b",
"size": "4.7GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "dolphin-llama3:70b",
"size": "40GB",
"context": "8K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "dolphin-mistral",
"url": "https://ollama.com/library/dolphin-mistral",
"description": "The uncensored Dolphin model based on Mistral that excels at coding tasks. Updated to version 2.8.",
"variantsCount": 2,
"variants": [
{
"name": "dolphin-mistral:latest",
"size": "4.1GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "dolphin-mistral:7b",
"size": "4.1GB",
"context": "32K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "dolphin-mixtral",
"url": "https://ollama.com/library/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.",
"variantsCount": 3,
"variants": [
{
"name": "dolphin-mixtral:latest",
"size": "26GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "dolphin-mixtral:8x7b",
"size": "26GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "dolphin-mixtral:8x22b",
"size": "80GB",
"context": "64K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "dolphin-phi",
"url": "https://ollama.com/library/dolphin-phi",
"description": "2.7B uncensored Dolphin model by Eric Hartford, based on the Phi language model by Microsoft Research.",
"variantsCount": 2,
"variants": [
{
"name": "dolphin-phi:latest",
"size": "1.6GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "dolphin-phi:2.7b",
"size": "1.6GB",
"context": "2K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "dolphin3",
"url": "https://ollama.com/library/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, enabling coding, math, agentic, function calling, and general use cases.",
"variantsCount": 2,
"variants": [
{
"name": "dolphin3:latest",
"size": "4.9GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "dolphin3:8b",
"size": "4.9GB",
"context": "128K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "dolphincoder",
"url": "https://ollama.com/library/dolphincoder",
"description": "A 7B and 15B uncensored variant of the Dolphin model family that excels at coding, based on StarCoder2.",
"variantsCount": 3,
"variants": [
{
"name": "dolphincoder:latest",
"size": "4.2GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "dolphincoder:7b",
"size": "4.2GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "dolphincoder:15b",
"size": "9.1GB",
"context": "16K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "duckdb-nsql",
"url": "https://ollama.com/library/duckdb-nsql",
"description": "7B parameter text-to-SQL model made by MotherDuck and Numbers Station.",
"variantsCount": 2,
"variants": [
{
"name": "duckdb-nsql:latest",
"size": "3.8GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "duckdb-nsql:7b",
"size": "3.8GB",
"context": "16K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "embeddinggemma",
"url": "https://ollama.com/library/embeddinggemma",
"description": "EmbeddingGemma is a 300M parameter embedding model from Google.",
"variantsCount": 2,
"variants": [
{
"name": "embeddinggemma:latest",
"size": "622MB",
"context": "2K",
"inputType": "Text"
},
{
"name": "embeddinggemma:300m",
"size": "622MB",
"context": "2K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "everythinglm",
"url": "https://ollama.com/library/everythinglm",
"description": "Uncensored Llama2 based model with support for a 16K context window.",
"variantsCount": 2,
"variants": [
{
"name": "everythinglm:latest",
"size": "7.4GB",
"context": "16K",
"inputType": "Text"
},
{
"name": "everythinglm:13b",
"size": "7.4GB",
"context": "16K",
"inputType": "Text"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"model": "exaone-deep",
"url": "https://ollama.com/library/exaone-deep",
"description": "EXAONE Deep exhibits superior capabilities in various reasoning tasks including math and coding benchmarks, ranging from 2.4B to 32B parameters developed and released by LG AI Research.",
"variantsCount": 4,
"variants": [
{
"name": "exaone-deep:latest",
"size": "4.8GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone-deep:2.4b",
"size": "1.6GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone-deep:7.8b",
"size": "4.8GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone-deep:32b",
"size": "19GB",
"context": "32K",
"inputType": "Text"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"model": "exaone3.5",
"url": "https://ollama.com/library/exaone3.5",
"description": "EXAONE 3.5 is a collection of instruction-tuned bilingual (English and Korean) generative models ranging from 2.4B to 32B parameters, developed and released by LG AI Research. ",
"variantsCount": 4,
"variants": [
{
"name": "exaone3.5:latest",
"size": "4.8GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone3.5:2.4b",
"size": "1.6GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone3.5:7.8b",
"size": "4.8GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "exaone3.5:32b",
"size": "19GB",
"context": "32K",
"inputType": "Text"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"model": "falcon",
"url": "https://ollama.com/library/falcon",
"description": "A large language model built by the Technology Innovation Institute (TII) for use in summarization, text generation, and chat bots.",
"variantsCount": 4,
"variants": [
{
"name": "falcon:latest",
"size": "4.2GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "falcon:7b",
"size": "4.2GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "falcon:40b",
"size": "24GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "falcon:180b",
"size": "101GB",
"context": "2K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "falcon2",
"url": "https://ollama.com/library/falcon2",
"description": "Falcon2 is an 11B parameters causal decoder-only model built by TII and trained over 5T tokens.",
"variantsCount": 2,
"variants": [
{
"name": "falcon2:latest",
"size": "6.4GB",
"context": "2K",
"inputType": "Text"
},
{
"name": "falcon2:11b",
"size": "6.4GB",
"context": "2K",
"inputType": "Text"
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"model": "falcon3",
"url": "https://ollama.com/library/falcon3",
"description": "A family of efficient AI models under 10B parameters performant in science, math, and coding through innovative training techniques.",
"variantsCount": 5,
"variants": [
{
"name": "falcon3:latest",
"size": "4.6GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "falcon3:1b",
"size": "1.8GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "falcon3:3b",
"size": "2.0GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "falcon3:7b",
"size": "4.6GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "falcon3:10b",
"size": "6.3GB",
"context": "32K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "firefunction-v2",
"url": "https://ollama.com/library/firefunction-v2",
"description": "An open weights function calling model based on Llama 3, competitive with GPT-4o function calling capabilities.",
"variantsCount": 2,
"variants": [
{
"name": "firefunction-v2:latest",
"size": "40GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "firefunction-v2:70b",
"size": "40GB",
"context": "8K",
"inputType": "Text"
}
]
}
+14
View File
@@ -0,0 +1,14 @@
{
"model": "gemini-3-pro-preview",
"url": "https://ollama.com/library/gemini-3-pro-preview",
"description": "Google's most intelligent model with SOTA reasoning and multimodal understanding, and powerful agentic and vibe coding capabilities.",
"variantsCount": 1,
"variants": [
{
"name": "gemini-3-pro-preview:latest",
"size": "-",
"context": "1M",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "gemma",
"url": "https://ollama.com/library/gemma",
"description": "Gemma is a family of lightweight, state-of-the-art open models built by Google DeepMind. Updated to version 1.1",
"variantsCount": 3,
"variants": [
{
"name": "gemma:latest",
"size": "5.0GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "gemma:2b",
"size": "1.7GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "gemma:7b",
"size": "5.0GB",
"context": "8K",
"inputType": "Text"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"model": "gemma2",
"url": "https://ollama.com/library/gemma2",
"description": "Google Gemma 2 is a high-performing and efficient model available in three sizes: 2B, 9B, and 27B.",
"variantsCount": 4,
"variants": [
{
"name": "gemma2:latest",
"size": "5.4GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "gemma2:2b",
"size": "1.6GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "gemma2:9b",
"size": "5.4GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "gemma2:27b",
"size": "16GB",
"context": "8K",
"inputType": "Text"
}
]
}
+44
View File
@@ -0,0 +1,44 @@
{
"model": "gemma3",
"url": "https://ollama.com/library/gemma3",
"description": "The current, most capable model that runs on a single GPU.",
"variantsCount": 6,
"variants": [
{
"name": "gemma3:latest",
"size": "3.3GB",
"context": "128K",
"inputType": "Text, Image"
},
{
"name": "gemma3:270m",
"size": "292MB",
"context": "32K",
"inputType": "Text"
},
{
"name": "gemma3:1b",
"size": "815MB",
"context": "32K",
"inputType": "Text"
},
{
"name": "gemma3:4b",
"size": "3.3GB",
"context": "128K",
"inputType": "Text, Image"
},
{
"name": "gemma3:12b",
"size": "8.1GB",
"context": "128K",
"inputType": "Text, Image"
},
{
"name": "gemma3:27b",
"size": "17GB",
"context": "128K",
"inputType": "Text, Image"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "gemma3n",
"url": "https://ollama.com/library/gemma3n",
"description": "Gemma 3n models are designed for efficient execution on everyday devices such as laptops, tablets or phones. ",
"variantsCount": 3,
"variants": [
{
"name": "gemma3n:latest",
"size": "7.5GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "gemma3n:e2b",
"size": "5.6GB",
"context": "32K",
"inputType": "Text"
},
{
"name": "gemma3n:e4b",
"size": "7.5GB",
"context": "32K",
"inputType": "Text"
}
]
}
+14
View File
@@ -0,0 +1,14 @@
{
"model": "glm-4.6",
"url": "https://ollama.com/library/glm-4.6",
"description": "Advanced agentic, reasoning and coding capabilities.",
"variantsCount": 1,
"variants": [
{
"name": "glm-4.6:cloud",
"size": "-",
"context": "198K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "glm4",
"url": "https://ollama.com/library/glm4",
"description": "A strong multi-lingual general language model with competitive performance to Llama 3.",
"variantsCount": 2,
"variants": [
{
"name": "glm4:latest",
"size": "5.5GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "glm4:9b",
"size": "5.5GB",
"context": "128K",
"inputType": "Text"
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"model": "goliath",
"url": "https://ollama.com/library/goliath",
"description": "A language model created by combining two fine-tuned Llama 2 70B models into one.",
"variantsCount": 16,
"variants": [
{
"name": "goliath:latest",
"size": "66GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q2_K",
"size": "50GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q3_K_S",
"size": "51GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q3_K_M",
"size": "56GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q3_K_L",
"size": "62GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q4_0",
"size": "66GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q4_1",
"size": "74GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q4_K_S",
"size": "66GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q4_K_M",
"size": "71GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q5_0",
"size": "81GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q5_1",
"size": "88GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q5_K_S",
"size": "81GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q5_K_M",
"size": "83GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q6_K",
"size": "97GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-q8_0",
"size": "125GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "goliath:120b-fp16",
"size": "236GB",
"context": "4K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "gpt-oss-safeguard",
"url": "https://ollama.com/library/gpt-oss-safeguard",
"description": "gpt-oss-safeguard-20b and gpt-oss-safeguard-120b are safety reasoning models built-upon gpt-oss",
"variantsCount": 3,
"variants": [
{
"name": "gpt-oss-safeguard:latest",
"size": "14GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss-safeguard:20b",
"size": "14GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss-safeguard:120b",
"size": "65GB",
"context": "128K",
"inputType": "Text"
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"model": "gpt-oss",
"url": "https://ollama.com/library/gpt-oss",
"description": "OpenAI’s open-weight models designed for powerful reasoning, agentic tasks, and versatile developer use cases.",
"variantsCount": 5,
"variants": [
{
"name": "gpt-oss:latest",
"size": "14GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss:20b",
"size": "14GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss:120b",
"size": "65GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss:20b-cloud",
"size": "-",
"context": "128K",
"inputType": "Text"
},
{
"name": "gpt-oss:120b-cloud",
"size": "-",
"context": "128K",
"inputType": "Text"
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"model": "granite-code",
"url": "https://ollama.com/library/granite-code",
"description": "A family of open foundation models by IBM for Code Intelligence",
"variantsCount": 5,
"variants": [
{
"name": "granite-code:latest",
"size": "2.0GB",
"context": "125K",
"inputType": "Text"
},
{
"name": "granite-code:3b",
"size": "2.0GB",
"context": "125K",
"inputType": "Text"
},
{
"name": "granite-code:8b",
"size": "4.6GB",
"context": "125K",
"inputType": "Text"
},
{
"name": "granite-code:20b",
"size": "12GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "granite-code:34b",
"size": "19GB",
"context": "8K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite-embedding",
"url": "https://ollama.com/library/granite-embedding",
"description": "The IBM Granite Embedding 30M and 278M models models are text-only dense biencoder embedding models, with 30M available in English only and 278M serving multilingual use cases.",
"variantsCount": 3,
"variants": [
{
"name": "granite-embedding:latest",
"size": "63MB",
"context": "512",
"inputType": "Text"
},
{
"name": "granite-embedding:30m",
"size": "63MB",
"context": "512",
"inputType": "Text"
},
{
"name": "granite-embedding:278m",
"size": "563MB",
"context": "512",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite3-dense",
"url": "https://ollama.com/library/granite3-dense",
"description": "The IBM Granite 2B and 8B models are designed to support tool-based use cases and support for retrieval augmented generation (RAG), streamlining code generation, translation and bug fixing.",
"variantsCount": 3,
"variants": [
{
"name": "granite3-dense:latest",
"size": "1.6GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "granite3-dense:2b",
"size": "1.6GB",
"context": "4K",
"inputType": "Text"
},
{
"name": "granite3-dense:8b",
"size": "4.9GB",
"context": "4K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite3-guardian",
"url": "https://ollama.com/library/granite3-guardian",
"description": "The IBM Granite Guardian 3.0 2B and 8B models are designed to detect risks in prompts and/or responses.",
"variantsCount": 3,
"variants": [
{
"name": "granite3-guardian:latest",
"size": "2.7GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "granite3-guardian:2b",
"size": "2.7GB",
"context": "8K",
"inputType": "Text"
},
{
"name": "granite3-guardian:8b",
"size": "5.8GB",
"context": "8K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite3-moe",
"url": "https://ollama.com/library/granite3-moe",
"description": "The IBM Granite 1B and 3B models are the first mixture of experts (MoE) Granite models from IBM designed for low latency usage.",
"variantsCount": 3,
"variants": [
{
"name": "granite3-moe:latest",
"size": "822MB",
"context": "4K",
"inputType": "Text"
},
{
"name": "granite3-moe:1b",
"size": "822MB",
"context": "4K",
"inputType": "Text"
},
{
"name": "granite3-moe:3b",
"size": "2.1GB",
"context": "4K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite3.1-dense",
"url": "https://ollama.com/library/granite3.1-dense",
"description": "The IBM Granite 2B and 8B models are text-only dense LLMs trained on over 12 trillion tokens of data, demonstrated significant improvements over their predecessors in performance and speed in IBM’s initial testing.",
"variantsCount": 3,
"variants": [
{
"name": "granite3.1-dense:latest",
"size": "5.0GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "granite3.1-dense:2b",
"size": "1.6GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "granite3.1-dense:8b",
"size": "5.0GB",
"context": "128K",
"inputType": "Text"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"model": "granite3.1-moe",
"url": "https://ollama.com/library/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.",
"variantsCount": 3,
"variants": [
{
"name": "granite3.1-moe:latest",
"size": "2.0GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "granite3.1-moe:1b",
"size": "1.4GB",
"context": "128K",
"inputType": "Text"
},
{
"name": "granite3.1-moe:3b",
"size": "2.0GB",
"context": "128K",
"inputType": "Text"
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"model": "granite3.2-vision",
"url": "https://ollama.com/library/granite3.2-vision",
"description": "A compact and efficient vision-language model, specifically designed for visual document understanding, enabling automated content extraction from tables, charts, infographics, plots, diagrams, and more.",
"variantsCount": 2,
"variants": [
{
"name": "granite3.2-vision:latest",
"size": "2.4GB",
"context": "16K",
"inputType": "Text, Image"
},
{
"name": "granite3.2-vision:2b",
"size": "2.4GB",
"context": "16K",
"inputType": "Text, Image"
}
]
}
Loaded 100 of 266 files, more files were not shown because too many files have changed in this diff. Show more