Merge pull request #1 from evandrodevbr/develop
feat: enhance chat functionality with markdown support and default prompts
This commit is contained in:
commit
7d169d8a61
266 files changed
+21098
-314
No files matched your search
+41
@@ -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
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
+122
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user