Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0b7db8c12 | ||
|
|
2835957e40 | ||
|
|
3e57b78aa7 | ||
|
|
cde8e5ac00 | ||
|
|
6fdbd4bfe6 | ||
|
|
d01d26e74f | ||
|
|
e51a54c568 | ||
|
|
a319a9cf8d | ||
|
|
1daa6c77ee | ||
|
|
3e5727ffc7 | ||
|
|
162a6f37f7 | ||
|
|
6c366eecb0 | ||
|
|
6cdc8d4294 | ||
|
|
7d169d8a61 |
No files matched your search
@@ -0,0 +1,186 @@
|
|||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
TAURI_VERSION: "2.9.4"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: 'windows-latest'
|
||||||
|
target: 'x86_64-pc-windows-msvc'
|
||||||
|
bundle: 'msi'
|
||||||
|
- platform: 'macos-latest'
|
||||||
|
target: 'aarch64-apple-darwin'
|
||||||
|
bundle: 'dmg'
|
||||||
|
- platform: 'macos-latest'
|
||||||
|
target: 'x86_64-apple-darwin'
|
||||||
|
bundle: 'dmg'
|
||||||
|
- platform: 'ubuntu-latest'
|
||||||
|
target: 'x86_64-unknown-linux-gnu'
|
||||||
|
bundle: 'appimage'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Install dependencies (Ubuntu only)
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
run: pnpm generate-changelog
|
||||||
|
|
||||||
|
- name: Build Tauri app
|
||||||
|
run: |
|
||||||
|
cd src-tauri
|
||||||
|
cargo build --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Build Tauri bundle
|
||||||
|
env:
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_UPDATER_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
cd src-tauri
|
||||||
|
if [ -n "$TAURI_PRIVATE_KEY" ]; then
|
||||||
|
echo "$TAURI_PRIVATE_KEY" > /tmp/private.key
|
||||||
|
cargo tauri build --target ${{ matrix.target }} --config '{"updater":{"active":true}}'
|
||||||
|
rm /tmp/private.key
|
||||||
|
else
|
||||||
|
cargo tauri build --target ${{ matrix.target }}
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.platform }}-${{ matrix.target }}
|
||||||
|
path: |
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/**/*.${{ matrix.bundle }}
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/**/*.exe
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/**/*.AppImage
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/**/*.deb
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: build
|
||||||
|
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Generate changelog for release
|
||||||
|
run: pnpm generate-changelog
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Extract release notes
|
||||||
|
id: extract_notes
|
||||||
|
run: |
|
||||||
|
if [ -f "data/changelog.json" ]; then
|
||||||
|
NOTES=$(node -e "
|
||||||
|
const fs = require('fs');
|
||||||
|
try {
|
||||||
|
const changelog = JSON.parse(fs.readFileSync('data/changelog.json', 'utf-8'));
|
||||||
|
const latestRelease = changelog.releases[0];
|
||||||
|
if (latestRelease) {
|
||||||
|
let notes = '## ' + (latestRelease.version === 'unreleased' ? 'Próxima Versão' : 'v' + latestRelease.version) + '\\n\\n';
|
||||||
|
notes += '**Data:** ' + latestRelease.date + '\\n\\n';
|
||||||
|
notes += '### Mudanças\\n\\n';
|
||||||
|
latestRelease.commits.forEach(commit => {
|
||||||
|
const icon = commit.type === 'feat' ? '✨' : '🐛';
|
||||||
|
notes += '- ' + icon + ' **' + (commit.type === 'feat' ? 'Nova Funcionalidade' : 'Correção') + ':** ' + commit.message + '\\n';
|
||||||
|
});
|
||||||
|
console.log(notes);
|
||||||
|
} else {
|
||||||
|
console.log('## Release ${{ steps.get_version.outputs.version }}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('## Release ${{ steps.get_version.outputs.version }}');
|
||||||
|
}
|
||||||
|
")
|
||||||
|
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$NOTES" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "notes=## Release ${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.get_version.outputs.tag }}
|
||||||
|
name: Release ${{ steps.get_version.outputs.tag }}
|
||||||
|
body: |
|
||||||
|
${{ steps.extract_notes.outputs.notes }}
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
Baixe o instalador para sua plataforma nos assets abaixo.
|
||||||
|
draft: false
|
||||||
|
files: artifacts/**/*
|
||||||
|
generate_release_notes: false
|
||||||
|
|
||||||
@@ -39,3 +39,4 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
~/.tauri/ollahub.key
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
## 📋 Análise
|
|
||||||
**Solicitação**: Corrigir falhas de conexão do browser headless e o erro de compilação `Send`, tornar o scraper resiliente com retry e ampliar resultados de busca.
|
|
||||||
**Objetivo real**: Garantir estabilidade contínua do backend de scraping, mesmo quando o Chrome headless cai, evitando violações de `Send` e melhorando a cobertura de fontes.
|
|
||||||
|
|
||||||
## 🔍 Contexto Identificado
|
|
||||||
### Arquivos Examinados
|
|
||||||
- `src-tauri/src/lib.rs:1176–1193` get_or_create_browser: cria/reutiliza `Browser` singleton.
|
|
||||||
- `src-tauri/src/lib.rs:1247–1277` search_web_metadata: comando Tauri que exige `Future + Send`.
|
|
||||||
- `src-tauri/src/web_scraper.rs:136–240` search_duckduckgo_metadata: parsing HTML, previously segurava `Html` atravessando `await`.
|
|
||||||
- `src-tauri/src/web_scraper.rs:469–538` search_and_scrape_with_config: coordena scraping paralelo e coleta resultados.
|
|
||||||
- `src-tauri/src/web_scraper.rs:605–736` fetch_and_convert_sync: cria aba, navega, extrai HTML e usa Readability.
|
|
||||||
- `src-tauri/src/lib.rs:1306–1399` force_kill_browser: encerra processos Chrome headless.
|
|
||||||
|
|
||||||
### Padrões Detectados
|
|
||||||
- **Singleton Browser** via `State<BrowserState>` com `Arc<Browser>`.
|
|
||||||
- **Concorrência controlada** com `Semaphore` e `tokio::spawn_blocking` para operações de Chrome.
|
|
||||||
- **Fallbacks**: limpeza de URL, filtros anti-ads, Readability + fallback por parágrafos.
|
|
||||||
|
|
||||||
### Dependências Mapeadas
|
|
||||||
```
|
|
||||||
web_scraper (modificado)
|
|
||||||
├─ headless_chrome::Browser/Tab
|
|
||||||
├─ reqwest (HTTP)
|
|
||||||
├─ scraper (Html, Selector) [não Send]
|
|
||||||
└─ readability, html2text
|
|
||||||
lib (modificado)
|
|
||||||
└─ tauri commands (Future + Send obrigatório)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Abordagens Viáveis
|
|
||||||
|
|
||||||
### Abordagem A: Verificação ativa de Browser + Retry cooperativo
|
|
||||||
**Conceito**: Validar liveness do `Browser` antes de uso; se cair durante scraping, recriar e repetir uma passada de URLs com limite de tentativas.
|
|
||||||
```
|
|
||||||
request → get_or_create_browser (is_alive?) → spawn scraping
|
|
||||||
↳ erro conn fechada → reset + recreate → retry restante (1x)
|
|
||||||
```
|
|
||||||
**Vantagens**: ✓ Simples de integrar, ✓ Minimiza impacto, ✓ Boa resiliência
|
|
||||||
**Desvantagens**: ✗ Retry global pode repetir algumas URLs
|
|
||||||
**Complexidade**: O(n) | Tempo: 3–4h | Risco: Médio
|
|
||||||
|
|
||||||
### Abordagem B: Pool/Auto-heal por task
|
|
||||||
**Conceito**: Cada task recupera o browser ao falhar e re-executa só sua URL.
|
|
||||||
```
|
|
||||||
spawn_blocking(url) → new_tab → erro conn → sinaliza reset → reobtem browser → reprocessa url
|
|
||||||
```
|
|
||||||
**Vantagens**: ✓ Isolamento por URL
|
|
||||||
**Desvantagens**: ✗ Coordenação de estado entre threads, ✗ Maior complexidade
|
|
||||||
**Complexidade**: O(n) | Tempo: 5–6h | Risco: Médio-Alto
|
|
||||||
|
|
||||||
## 🏆 Recomendação
|
|
||||||
**Abordagem A** porque:
|
|
||||||
- Alinha com o padrão singleton já existente.
|
|
||||||
- Menor acoplamento entre tarefas e estado global.
|
|
||||||
- Implementação direta com menor risco de condições de corrida.
|
|
||||||
|
|
||||||
## ⚠️ Riscos & Mitigações
|
|
||||||
| Risco | Prob | Impacto | Mitigação |
|
|
||||||
|-------|------|---------|-----------|
|
|
||||||
| Loop de retry infinito | Baixa | Médio | Limitar a 1–2 tentativas, backoff curto |
|
|
||||||
| Recriação concorrente do Browser | Média | Médio | Guardar lock no `BrowserState` e checar antes de recriar |
|
|
||||||
| Demasiadas abas simultâneas | Média | Médio | Reduzir `max_concurrent_tabs` ao detectar falha |
|
|
||||||
| DDG bloqueios 429 | Média | Baixo | Rotação User-Agent e backoff |
|
|
||||||
|
|
||||||
## 📝 Plano de Execução
|
|
||||||
1. Ajustes `Send` (consolidação)
|
|
||||||
- Isolar parsing `scraper::Html` em escopo síncrono que retorna `Vec<SearchResultMetadata>` (já aplicado em parte na função; revisar para todos os caminhos).
|
|
||||||
- Garantir que nenhum `Html`/`Selector` vive além do `await` nos métodos `search_*`.
|
|
||||||
- Verificar pontos: `src-tauri/src/web_scraper.rs:136–240` e demais que usem `Html`.
|
|
||||||
|
|
||||||
2. Browser Resurrection
|
|
||||||
- Adicionar `fn is_browser_alive(browser: &Browser) -> bool` em `web_scraper.rs` ou `lib.rs` (ex.: tentar `new_tab()` e descartar; ou `get_tabs()`; se falhar → false).
|
|
||||||
- Alterar `get_or_create_browser` (`src-tauri/src/lib.rs:1176–1193`) para: se `Some(browser)` e `!is_alive(browser)` → `reset` e `create_browser()`.
|
|
||||||
- Em `force_kill_browser` (`src-tauri/src/lib.rs:1306–1399`): após kill, setar `BrowserState` para `None` via lock (ou orientar UI a chamar `reset_browser` antes do kill), garantindo recriação no próximo uso.
|
|
||||||
|
|
||||||
3. Retry resiliente no Scraper
|
|
||||||
- Envolver `fetch_and_convert_sync` com camada de retry (máx. 2 tentativas) na orquestração async (`search_and_scrape_with_config` em `src-tauri/src/web_scraper.rs:469–538`).
|
|
||||||
- Se erro contiver "underlying connection is closed"/timeout global do browser:
|
|
||||||
- Executar `reset_browser` e `get_or_create_browser` para novo `Arc<Browser)`.
|
|
||||||
- Reprocessar as URLs que falharam apenas uma vez com o novo browser.
|
|
||||||
- Reduzir temporariamente `max_concurrent_tabs` para 3 ao detectar queda, evitando sobrecarga imediata.
|
|
||||||
|
|
||||||
4. Paginação no DuckDuckGo
|
|
||||||
- Em `search_duckduckgo` (`src-tauri/src/web_scraper.rs:78–133`): suportar paginação via parâmetro `s=<offset>` até atingir `limit` (30–50).
|
|
||||||
- Extrair links por página, deduplicar, respeitar `excluded_domains`/ads.
|
|
||||||
|
|
||||||
5. Telemetria e Logs
|
|
||||||
- Logar eventos de recreação de browser, tentativas de retry e motivos.
|
|
||||||
- Contadores: quedas de browser, tentativas, tempo médio por URL.
|
|
||||||
|
|
||||||
6. Testes/Validação
|
|
||||||
- Caso 1: Forçar `force_kill_browser` durante scraping → validar que a próxima chamada recria o browser e conclui.
|
|
||||||
- Caso 2: Simular DDG com >= 40 resultados → paginar e coletar sem violar `Send`.
|
|
||||||
- Caso 3: Stress com `max_concurrent_tabs=5` → se cair, reduzir e recuperar.
|
|
||||||
|
|
||||||
## Key Changes (Resumo de Implementação)
|
|
||||||
- `lib.rs:1176–1193`: `get_or_create_browser` verifica liveness; recria se morto.
|
|
||||||
- `lib.rs:1306–1399`: sincronia com `reset_browser` ao matar processos.
|
|
||||||
- `web_scraper.rs:469–538`: retry global (1x) e recriação de browser ao detectar conexão fechada; ajuste dinamicamente `Semaphore` na segunda passada.
|
|
||||||
- `web_scraper.rs:78–133`: paginação DDG com `s=` offset.
|
|
||||||
- `web_scraper.rs:136–240`: parsing isolado; garantir que `Html` é dropado antes do `await`.
|
|
||||||
|
|
||||||
## Entregáveis
|
|
||||||
- Código robusto em `web_scraper.rs` e `lib.rs` com:
|
|
||||||
- Correção total do `Send`.
|
|
||||||
- Resurreição automática do browser.
|
|
||||||
- Retry controlado por erro de conexão.
|
|
||||||
- Paginação para 30–50 resultados.
|
|
||||||
- Logs melhorados e validação manual automática.
|
|
||||||
|
|
||||||
## ❓ Aguardando Aprovação
|
|
||||||
- [ ] Posso prosseguir com as mudanças propostas?
|
|
||||||
- [ ] Preferências de `max_concurrent_tabs` e `limit` padrão (sugestão: 5 e 40)?
|
|
||||||
- [ ] Deseja manter `force_kill_browser` como utilitário avançado, com alerta na UI para reiniciar o scraper?
|
|
||||||
@@ -1,36 +1,81 @@
|
|||||||
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).
|
<div align="center">
|
||||||
|
|
||||||
## Getting Started
|
<img src="assets/readme/hero.svg" width="100%" alt="OllaHub: desktop chat for your local Ollama models, with web search and MCP tools">
|
||||||
|
|
||||||
First, run the development server:
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
[Quick start](#quick-start) · [Docs](docs/) · [Issues](https://git.evandro.dev.br/evandro/OllaHub/issues)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
|
||||||
|
OllaHub is a desktop chat app for the models you run with [Ollama](https://ollama.com). On first run it scans your hardware, recommends a model and downloads it. After that it chats with streaming, keeps history in a local SQLite database, and can add context from web search, MCP tools and scheduled tasks. The UI is Next.js inside a Tauri 2 window, and a Rust backend does the work.
|
||||||
|
|
||||||
|
OllaHub is a client, not a model runtime. It talks to the Ollama API (default `http://localhost:11434`, configurable in Settings), tries to start Ollama when it is installed but stopped, and can download the Ollama installer when it is missing.
|
||||||
|
|
||||||
|
## Why use it
|
||||||
|
|
||||||
|
- **Local by default.** Prompts go to your own Ollama, and chats live in `ollahub.db` in the app data directory, with FTS5 full-text search. Besides downloads (models, the embedding model, updates), web search is what reaches the internet.
|
||||||
|
- **Web answers ranked on your machine.** Headless Chrome scrapes Google, Bing, Yahoo, DuckDuckGo and Startpage (order configurable), then local embeddings (`all-MiniLM-L6-v2` on ONNX Runtime) rank and prune the pages, with a BM25-like fallback when the embedding model is unavailable. A deep-research mode runs planning → searching → aggregating → validating → formulating.
|
||||||
|
- **More than a chat box.** MCP servers run as child processes configured in `mcp_config.json`, their tools can be called from the chat, and cron tasks (search and summarize, notification, custom prompt) keep running in the background.
|
||||||
|
|
||||||
|
**When not to use it:** if you want a finished, tested product (version `0.1.0`, no test suite), a model runtime of its own, or a server you can run without a desktop.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- **Frontend** (`app/`, `components/`, `hooks/`, `lib/`, `store/`): Next.js 16 with `output: 'export'`, React 19, Tailwind 4, Radix UI and Zustand. It calls the backend through Tauri `invoke` and events.
|
||||||
|
- **Backend** (`src-tauri/src/`): `lib.rs` holds the commands for chat (`chat_stream`), models, the Ollama lifecycle, sessions, MCP, search, tasks, export and the installer. `db.rs` handles SQLite, `web_scraper.rs` the search engines, `embeddings.rs` the ranking, and `scheduler.rs` the cron tasks stored in `tasks.json`.
|
||||||
|
- **Models:** list, delete and pull with progress, install a local `.gguf` file, and get a recommendation for your hardware.
|
||||||
|
- **System monitor:** CPU and RAM stats plus GPU VRAM, usage and temperature. GPU detection uses `wmic` and `nvidia-smi` on Windows and `lspci` on Linux.
|
||||||
|
- **Updates:** the Tauri updater checks automatically. Its endpoint in `src-tauri/tauri.conf.json` points to a GitHub Releases `latest.json`.
|
||||||
|
|
||||||
|
More notes: [`docs/updater-setup.md`](docs/updater-setup.md) (signing keys and updater) and [`docs/style-guide.md`](docs/style-guide.md) (UI spacing, radii, accessibility).
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
No release is published on this repository yet, so build it locally. You need Node.js 20, pnpm 8, a stable Rust toolchain and the [Tauri 2 prerequisites](https://tauri.app/start/prerequisites/) (the versions come from the CI workflow). On Ubuntu the workflow also installs `libwebkit2gtk-4.1-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf`. Ollama is needed at runtime.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
git clone https://git.evandro.dev.br/evandro/OllaHub.git
|
||||||
# or
|
cd OllaHub
|
||||||
yarn dev
|
pnpm install
|
||||||
# or
|
|
||||||
pnpm dev
|
pnpm tauri dev # starts `pnpm dev` (Next.js on http://localhost:3000) inside the Tauri window
|
||||||
# or
|
pnpm tauri build # runs `pnpm generate-changelog && pnpm build`, then bundles the installer
|
||||||
bun dev
|
pnpm lint # ESLint
|
||||||
|
pnpm bump-version # patch | minor | major, optional --commit
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
The workflow [`.github/workflows/release.yml`](.github/workflows/release.yml) builds `msi` (Windows), `dmg` (macOS, Apple Silicon and Intel) and `AppImage` (Linux) on pushes to `main`, `master` and `develop`, and publishes release `v<version>` on pushes to `main` or `master`.
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
> [!WARNING]
|
||||||
|
> **Two things the app does on its own.**
|
||||||
|
> 1. Opening the chat page pulls the small helper model **`qwen2.5:0.5b`** if it is not installed yet, used for automatic chat titles. Expect an extra download on first use.
|
||||||
|
> 2. **Closing the window only hides it.** The close request is intercepted (`prevent_close`) so scheduled tasks keep running, but there is no tray icon to bring the window back yet; the tray step is still pending in [`IMPLEMENTATION_GUIDE.md`](IMPLEMENTATION_GUIDE.md).
|
||||||
|
|
||||||
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.
|
## Project status
|
||||||
|
|
||||||
## Learn More
|
Early: version `0.1.0`, 47 commits between 2025-10-27 and 2025-11-27, no automated tests (`package.json` defines `dev`, `build`, `start`, `lint`, `generate-changelog` and `bump-version`). Known limitations:
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
- **No tray icon yet:** closing the window hides it (see the warning above).
|
||||||
|
- **"Start with the system" does nothing yet:** the setting only toggles a stored flag; the code has a `TODO` until `tauri-plugin-autostart` is configured.
|
||||||
|
- **RAG inside `chat_stream` is not wired:** the `enable_rag` branch is commented out as `TODO`; the frontend assembles web context before the request.
|
||||||
|
- **Embeddings outside Windows:** the ONNX Runtime library is downloaded automatically only on Windows. On macOS and Linux, provide it in the app data folder or on the system path, otherwise ranking falls back to BM25-like scoring.
|
||||||
|
- **Unused leftovers:** `src-tauri/scripts/scraper.py` is not called by the Rust backend, and the 188 model files in `data/models/` are not referenced by the app code.
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
## License
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
No published license: all rights reserved.
|
||||||
|
|
||||||
## Deploy on Vercel
|
Built with [Tauri](https://tauri.app), [Next.js](https://nextjs.org), [Radix UI](https://www.radix-ui.com), [Tailwind CSS](https://tailwindcss.com), [Zustand](https://zustand-demo.pmnd.rs), [Lucide](https://lucide.dev), [headless_chrome](https://github.com/rust-headless-chrome/rust-headless-chrome), [ONNX Runtime](https://onnxruntime.ai) and the [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) model.
|
||||||
|
|
||||||
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.
|
<div align="center">
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
<sub>Made by <a href="https://evandro.dev.br">Evandro Fonseca Junior</a> · <a href="https://git.evandro.dev.br/evandro">git.evandro.dev.br</a></sub>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
import { AppUpdaterProvider } from "@/components/app-updater-provider";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -35,6 +36,7 @@ export default function RootLayout({
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
<AppUpdaterProvider />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+90
-1
@@ -24,11 +24,13 @@ import { useSettingsStore } from '@/store/settings-store';
|
|||||||
import { DomainTagsInput } from '@/components/settings/domain-tags-input';
|
import { DomainTagsInput } from '@/components/settings/domain-tags-input';
|
||||||
import { HardwareDashboard } from '@/components/settings/HardwareDashboard';
|
import { HardwareDashboard } from '@/components/settings/HardwareDashboard';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { CheckCircle2, XCircle, Loader2, Download, Trash2, Copy, ExternalLink, Plus, X, BookOpen, GraduationCap, Newspaper, Code, DollarSign, Edit, RotateCcw, Terminal, Power, ArrowLeft } from 'lucide-react';
|
import { CheckCircle2, XCircle, Loader2, Download, Trash2, Copy, ExternalLink, Plus, X, BookOpen, GraduationCap, Newspaper, Code, DollarSign, Edit, RotateCcw, Terminal, Power, ArrowLeft, Sparkles, RefreshCw } from 'lucide-react';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { SourcesConfig, SourceCategory } from '@/lib/types';
|
import { SourcesConfig, SourceCategory } from '@/lib/types';
|
||||||
import { TitleBar } from '@/components/titlebar';
|
import { TitleBar } from '@/components/titlebar';
|
||||||
|
import { useAppUpdater } from '@/hooks/use-app-updater';
|
||||||
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
|
|
||||||
interface Model {
|
interface Model {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -52,12 +54,20 @@ export default function SettingsPage() {
|
|||||||
const [isSavingSources, setIsSavingSources] = useState(false);
|
const [isSavingSources, setIsSavingSources] = useState(false);
|
||||||
const [recentLogs, setRecentLogs] = useState<string[]>([]);
|
const [recentLogs, setRecentLogs] = useState<string[]>([]);
|
||||||
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
|
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
|
||||||
|
const [appVersion, setAppVersion] = useState<string>('');
|
||||||
|
|
||||||
|
const updater = useAppUpdater();
|
||||||
|
|
||||||
// Load models, storage path, and sources config on mount
|
// Load models, storage path, and sources config on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadModels();
|
loadModels();
|
||||||
loadStoragePath();
|
loadStoragePath();
|
||||||
settings.fetchSources();
|
settings.fetchSources();
|
||||||
|
|
||||||
|
// Load app version
|
||||||
|
getVersion()
|
||||||
|
.then(setAppVersion)
|
||||||
|
.catch(() => setAppVersion('0.1.0'));
|
||||||
}, [settings.ollamaUrl]);
|
}, [settings.ollamaUrl]);
|
||||||
|
|
||||||
// Load logs periodically
|
// Load logs periodically
|
||||||
@@ -1095,6 +1105,85 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Atualizações</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure verificação automática de atualizações
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="auto-check-updates">Verificar Atualizações Automaticamente</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
O app verificará novas versões periodicamente
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="auto-check-updates"
|
||||||
|
checked={settings.autoCheckUpdates}
|
||||||
|
onCheckedChange={settings.setAutoCheckUpdates}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Versão Atual</span>
|
||||||
|
<span className="font-mono font-medium">
|
||||||
|
{appVersion || 'Carregando...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{updater.updateAvailable && (
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Nova Versão Disponível</span>
|
||||||
|
<span className="font-mono font-medium text-primary">
|
||||||
|
{updater.updateInfo?.version}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await updater.checkUpdate();
|
||||||
|
if (updater.updateAvailable) {
|
||||||
|
toast({
|
||||||
|
title: 'Atualização disponível',
|
||||||
|
description: `Versão ${updater.updateInfo?.version} está disponível para download.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'Você está atualizado',
|
||||||
|
description: 'Não há novas atualizações disponíveis.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
disabled={updater.isChecking}
|
||||||
|
>
|
||||||
|
{updater.isChecking ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Verificando...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
Verificar Atualizações
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{updater.error && (
|
||||||
|
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||||
|
<p className="text-sm text-destructive">{updater.error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="420" viewBox="0 0 1200 420" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">OllaHub</title>
|
||||||
|
<desc id="desc">OllaHub: a desktop window with a chat sidebar, a local Ollama model download in progress, an answer backed by numbered web sources, and Web and MCP toggles in the message box.</desc>
|
||||||
|
|
||||||
|
<rect width="1200" height="420" rx="24" fill="#09090B"/>
|
||||||
|
|
||||||
|
<g id="title-block" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
|
||||||
|
<text x="64" y="170" font-size="64" font-weight="700" fill="#FAFAFA">OllaHub</text>
|
||||||
|
<text x="64" y="226" font-size="28" fill="#FAFAFA">Desktop chat for your local Ollama</text>
|
||||||
|
<text x="64" y="264" font-size="28" fill="#FAFAFA">models, with web search and MCP.</text>
|
||||||
|
<text x="64" y="322" font-size="20" fill="#9F9FA9" font-family="ui-monospace, 'SF Mono', Consolas, monospace">Tauri 2 · Rust · Next.js 16 · React 19</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="window" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
|
||||||
|
<rect x="608" y="52" width="528" height="316" rx="14" fill="#09090B" stroke="#27272A" stroke-width="2"/>
|
||||||
|
<path d="M622 52 h500 a14 14 0 0 1 14 14 v26 h-528 v-26 a14 14 0 0 1 14 -14 z" fill="#18181B"/>
|
||||||
|
<text x="628" y="79" font-size="18" fill="#9F9FA9">OllaHub</text>
|
||||||
|
<g fill="none" stroke="#9F9FA9" stroke-width="2" stroke-linecap="round">
|
||||||
|
<path d="M1052 72 h14"/>
|
||||||
|
<rect x="1078" y="65" width="14" height="14" rx="2"/>
|
||||||
|
<path d="M1106 65 l14 14 M1120 65 l-14 14"/>
|
||||||
|
</g>
|
||||||
|
<path d="M608 92 h528" stroke="#27272A" stroke-width="2"/>
|
||||||
|
|
||||||
|
<path d="M608 92 h144 v276 h-130 a14 14 0 0 1 -14 -14 z" fill="#18181B"/>
|
||||||
|
<rect x="622" y="108" width="116" height="34" rx="8" fill="#1447E6"/>
|
||||||
|
<text x="680" y="131" font-size="18" fill="#FAFAFA" text-anchor="middle">New chat</text>
|
||||||
|
<rect x="622" y="166" width="100" height="10" rx="5" fill="#27272A"/>
|
||||||
|
<rect x="622" y="192" width="84" height="10" rx="5" fill="#27272A"/>
|
||||||
|
<rect x="622" y="218" width="94" height="10" rx="5" fill="#27272A"/>
|
||||||
|
<rect x="622" y="244" width="72" height="10" rx="5" fill="#27272A"/>
|
||||||
|
|
||||||
|
<text x="772" y="128" font-size="18" fill="#FAFAFA" font-family="ui-monospace, 'SF Mono', Consolas, monospace">qwen2.5:0.5b</text>
|
||||||
|
<rect x="772" y="140" width="344" height="10" rx="5" fill="#27272A"/>
|
||||||
|
<rect x="772" y="140" width="220" height="10" rx="5" fill="#1447E6"/>
|
||||||
|
|
||||||
|
<rect x="772" y="170" width="344" height="116" rx="12" fill="#201E25"/>
|
||||||
|
<rect x="790" y="190" width="290" height="10" rx="5" fill="#9F9FA9"/>
|
||||||
|
<rect x="790" y="212" width="300" height="10" rx="5" fill="#9F9FA9"/>
|
||||||
|
<rect x="790" y="234" width="180" height="10" rx="5" fill="#9F9FA9"/>
|
||||||
|
<text x="790" y="272" font-size="18" fill="#9F9FA9">Sources</text>
|
||||||
|
<g font-size="18" fill="#FAFAFA" text-anchor="middle">
|
||||||
|
<rect x="866" y="254" width="26" height="26" rx="13" fill="#27272A"/>
|
||||||
|
<text x="879" y="273">1</text>
|
||||||
|
<rect x="898" y="254" width="26" height="26" rx="13" fill="#27272A"/>
|
||||||
|
<text x="911" y="273">2</text>
|
||||||
|
<rect x="930" y="254" width="26" height="26" rx="13" fill="#27272A"/>
|
||||||
|
<text x="943" y="273">3</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect x="772" y="302" width="344" height="40" rx="20" fill="#18181B" stroke="#27272A" stroke-width="2"/>
|
||||||
|
<rect x="782" y="309" width="60" height="26" rx="13" fill="#27272A"/>
|
||||||
|
<text x="812" y="328" font-size="18" fill="#FAFAFA" text-anchor="middle">Web</text>
|
||||||
|
<rect x="850" y="309" width="62" height="26" rx="13" fill="#27272A"/>
|
||||||
|
<text x="881" y="328" font-size="18" fill="#FAFAFA" text-anchor="middle">MCP</text>
|
||||||
|
<circle cx="1096" cy="322" r="13" fill="#1447E6"/>
|
||||||
|
<path d="M1096 328 v-12 M1090 321 l6 -6 l6 6" fill="none" stroke="#FAFAFA" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { UpdateDialog } from '@/components/modals/update-dialog';
|
||||||
|
import { useAppUpdater } from '@/hooks/use-app-updater';
|
||||||
|
import { useSettingsStore } from '@/store/settings-store';
|
||||||
|
|
||||||
|
export function AppUpdaterProvider() {
|
||||||
|
const { updateAvailable, checkUpdate } = useAppUpdater();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [hasChecked, setHasChecked] = useState(false);
|
||||||
|
const autoCheckEnabled = useSettingsStore((state) => state.autoCheckUpdates ?? true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoCheckEnabled || hasChecked) return;
|
||||||
|
|
||||||
|
// Verificar após um delay para não interferir no carregamento inicial
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
checkUpdate().then(() => {
|
||||||
|
setHasChecked(true);
|
||||||
|
});
|
||||||
|
}, 5000); // 5 segundos após o app carregar
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [autoCheckEnabled, hasChecked, checkUpdate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updateAvailable && !isOpen) {
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
}, [updateAvailable, isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UpdateDialog
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Download, Loader2, X, Sparkles } from 'lucide-react';
|
||||||
|
import { useAppUpdater } from '@/hooks/use-app-updater';
|
||||||
|
import { useSettingsStore } from '@/store/settings-store';
|
||||||
|
|
||||||
|
interface UpdateDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdateDialog({ open, onOpenChange }: UpdateDialogProps) {
|
||||||
|
const {
|
||||||
|
currentVersion,
|
||||||
|
updateAvailable,
|
||||||
|
updateInfo,
|
||||||
|
isChecking,
|
||||||
|
isDownloading,
|
||||||
|
downloadProgress,
|
||||||
|
error,
|
||||||
|
installUpdate,
|
||||||
|
} = useAppUpdater();
|
||||||
|
|
||||||
|
const [dontShowAgain, setDontShowAgain] = useState(false);
|
||||||
|
const setAutoCheckUpdates = useSettingsStore((state) => state.setAutoCheckUpdates);
|
||||||
|
|
||||||
|
const handleInstall = async () => {
|
||||||
|
if (dontShowAgain) {
|
||||||
|
setAutoCheckUpdates(false);
|
||||||
|
}
|
||||||
|
await installUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (dontShowAgain) {
|
||||||
|
setAutoCheckUpdates(false);
|
||||||
|
}
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!updateAvailable && !isChecking) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open && updateAvailable} onOpenChange={handleClose}>
|
||||||
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5 text-primary" />
|
||||||
|
Atualização Disponível
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Uma nova versão do OllaHub está disponível para download.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Versão Atual</span>
|
||||||
|
<span className="font-mono font-medium">{currentVersion}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Nova Versão</span>
|
||||||
|
<span className="font-mono font-medium text-primary">
|
||||||
|
{updateInfo?.version}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{updateInfo?.body && (
|
||||||
|
<div className="p-3 bg-muted rounded-lg">
|
||||||
|
<p className="text-sm text-muted-foreground line-clamp-3">
|
||||||
|
{updateInfo.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isDownloading && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Baixando atualização...</span>
|
||||||
|
<span className="font-medium">{downloadProgress}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={downloadProgress} className="h-2" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||||
|
<p className="text-sm text-destructive">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id="dont-show-updates"
|
||||||
|
checked={dontShowAgain}
|
||||||
|
onCheckedChange={(checked) => setDontShowAgain(checked === true)}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="dont-show-updates"
|
||||||
|
className="text-sm font-normal cursor-pointer"
|
||||||
|
>
|
||||||
|
Não verificar atualizações automaticamente
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={isDownloading}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 mr-2" />
|
||||||
|
Depois
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleInstall}
|
||||||
|
disabled={isDownloading || isChecking}
|
||||||
|
>
|
||||||
|
{isDownloading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
Baixando...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Download className="h-4 w-4 mr-2" />
|
||||||
|
Atualizar Agora
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Configuração do Sistema de Atualização Automática
|
||||||
|
|
||||||
|
Este documento explica como configurar o sistema de atualização automática do OllaHub.
|
||||||
|
|
||||||
|
## Pré-requisitos
|
||||||
|
|
||||||
|
1. Repositório GitHub configurado
|
||||||
|
2. Rust e Cargo instalados
|
||||||
|
3. Tauri CLI instalado
|
||||||
|
|
||||||
|
## Passo 1: Gerar Chaves de Assinatura
|
||||||
|
|
||||||
|
Execute o script para gerar as chaves:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/generate-updater-keys.sh
|
||||||
|
./scripts/generate-updater-keys.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou manualmente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.tauri
|
||||||
|
cargo tauri signer generate -w ~/.tauri/ollahub.key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Passo 2: Configurar Chave Pública
|
||||||
|
|
||||||
|
1. Copie a chave pública gerada (está em `~/.tauri/ollahub.key.pub`)
|
||||||
|
2. Edite `src-tauri/tauri.conf.json`
|
||||||
|
3. Substitua `YOUR_PUBLIC_KEY_HERE` pela chave pública
|
||||||
|
4. Atualize o endpoint com seu repositório GitHub:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"updater": {
|
||||||
|
"active": true,
|
||||||
|
"endpoints": [
|
||||||
|
"https://api.github.com/repos/SEU_USUARIO/SEU_REPOSITORIO/releases/latest"
|
||||||
|
],
|
||||||
|
"dialog": true,
|
||||||
|
"pubkey": "SUA_CHAVE_PUBLICA_AQUI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Passo 3: Configurar GitHub Secrets
|
||||||
|
|
||||||
|
1. Vá em **Settings** > **Secrets and variables** > **Actions**
|
||||||
|
2. Clique em **New repository secret**
|
||||||
|
3. Nome: `TAURI_UPDATER_PRIVATE_KEY`
|
||||||
|
4. Valor: Cole o conteúdo completo do arquivo `~/.tauri/ollahub.key`
|
||||||
|
|
||||||
|
## Passo 4: Atualizar Workflow (se necessário)
|
||||||
|
|
||||||
|
O workflow `.github/workflows/release.yml` já está configurado para:
|
||||||
|
- Buildar para Windows, macOS e Linux
|
||||||
|
- Assinar binários com a chave privada
|
||||||
|
- Criar release automaticamente quando há push em `main`/`master`
|
||||||
|
|
||||||
|
## Como Funciona
|
||||||
|
|
||||||
|
1. **Build Automático**: Quando você faz push para `main`/`master`, o GitHub Actions:
|
||||||
|
- Builda o app para todas as plataformas
|
||||||
|
- Assina os binários
|
||||||
|
- Cria uma release no GitHub
|
||||||
|
|
||||||
|
2. **Verificação de Atualizações**: O app verifica automaticamente:
|
||||||
|
- 5 segundos após iniciar (primeira verificação)
|
||||||
|
- A cada 6 horas (verificação periódica)
|
||||||
|
- Manualmente via botão nas configurações
|
||||||
|
|
||||||
|
3. **Instalação**: Quando há atualização disponível:
|
||||||
|
- Usuário recebe notificação
|
||||||
|
- Pode escolher atualizar agora ou depois
|
||||||
|
- Download e instalação são automáticos
|
||||||
|
- App reinicia automaticamente após instalação
|
||||||
|
|
||||||
|
## Bump de Versão
|
||||||
|
|
||||||
|
Para criar uma nova versão:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Patch (0.1.0 -> 0.1.1)
|
||||||
|
pnpm run bump-version patch --commit
|
||||||
|
|
||||||
|
# Minor (0.1.0 -> 0.2.0)
|
||||||
|
pnpm run bump-version minor --commit
|
||||||
|
|
||||||
|
# Major (0.1.0 -> 1.0.0)
|
||||||
|
pnpm run bump-version major --commit
|
||||||
|
```
|
||||||
|
|
||||||
|
Depois, faça push:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push && git push --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
O GitHub Actions criará automaticamente a release.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Erro: "Failed to check for updates"
|
||||||
|
- Verifique se o endpoint no `tauri.conf.json` está correto
|
||||||
|
- Verifique se há releases públicas no GitHub
|
||||||
|
- Verifique a conexão com a internet
|
||||||
|
|
||||||
|
### Erro: "Signature verification failed"
|
||||||
|
- Verifique se a chave pública no `tauri.conf.json` está correta
|
||||||
|
- Verifique se a chave privada no GitHub Secret está correta
|
||||||
|
- Regere as chaves se necessário
|
||||||
|
|
||||||
|
### Release não é criada automaticamente
|
||||||
|
- Verifique se o push foi feito para `main` ou `master`
|
||||||
|
- Verifique os logs do GitHub Actions
|
||||||
|
- Verifique se as permissões do workflow estão corretas
|
||||||
|
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
|
import { useSettingsStore } from '@/store/settings-store';
|
||||||
|
import { check as checkForUpdate } from '@tauri-apps/plugin-updater';
|
||||||
|
|
||||||
|
export interface UpdateInfo {
|
||||||
|
version: string;
|
||||||
|
date?: string;
|
||||||
|
body?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppUpdaterState {
|
||||||
|
currentVersion: string;
|
||||||
|
updateAvailable: boolean;
|
||||||
|
updateInfo: UpdateInfo | null;
|
||||||
|
isChecking: boolean;
|
||||||
|
isDownloading: boolean;
|
||||||
|
downloadProgress: number;
|
||||||
|
error: string | null;
|
||||||
|
checkUpdate: () => Promise<void>;
|
||||||
|
installUpdate: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAppUpdater(): AppUpdaterState {
|
||||||
|
const [currentVersion, setCurrentVersion] = useState<string>('');
|
||||||
|
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const autoCheckEnabled = useSettingsStore((state) => state.autoCheckUpdates ?? true);
|
||||||
|
|
||||||
|
// Obter versão atual
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchVersion = async () => {
|
||||||
|
try {
|
||||||
|
const version = await getVersion();
|
||||||
|
setCurrentVersion(version);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get app version:', err);
|
||||||
|
setCurrentVersion('0.1.0');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchVersion();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Verificar atualizações
|
||||||
|
const checkUpdate = useCallback(async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const update = await checkForUpdate();
|
||||||
|
|
||||||
|
if (update?.available) {
|
||||||
|
setUpdateAvailable(true);
|
||||||
|
setUpdateInfo({
|
||||||
|
version: update.version,
|
||||||
|
date: update.date,
|
||||||
|
body: update.body,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setUpdateAvailable(false);
|
||||||
|
setUpdateInfo(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to check for updates:', err);
|
||||||
|
setError(err instanceof Error ? err.message : 'Erro ao verificar atualizações');
|
||||||
|
setUpdateAvailable(false);
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Instalar atualização
|
||||||
|
const installUpdate = useCallback(async () => {
|
||||||
|
if (!updateAvailable) return;
|
||||||
|
|
||||||
|
setIsDownloading(true);
|
||||||
|
setError(null);
|
||||||
|
setDownloadProgress(0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const update = await checkForUpdate();
|
||||||
|
|
||||||
|
if (update?.available) {
|
||||||
|
// Escutar progresso do download
|
||||||
|
const unlisten = await update.on('download-progress', (progress: any) => {
|
||||||
|
setDownloadProgress(progress.progress || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Baixar e instalar atualização
|
||||||
|
await update.downloadAndInstall();
|
||||||
|
|
||||||
|
unlisten();
|
||||||
|
|
||||||
|
// Reiniciar aplicativo após instalação
|
||||||
|
await update.installAndRestart();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to install update:', err);
|
||||||
|
setError(err instanceof Error ? err.message : 'Erro ao instalar atualização');
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
}, [updateAvailable]);
|
||||||
|
|
||||||
|
// Verificação automática periódica (a cada 6 horas)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoCheckEnabled) return;
|
||||||
|
|
||||||
|
// Verificar imediatamente ao montar (com delay para não interferir no carregamento)
|
||||||
|
const initialTimer = setTimeout(() => {
|
||||||
|
checkUpdate();
|
||||||
|
}, 5000); // 5 segundos após o app carregar
|
||||||
|
|
||||||
|
// Verificar periodicamente
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
checkUpdate();
|
||||||
|
}, 6 * 60 * 60 * 1000); // 6 horas
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(initialTimer);
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [autoCheckEnabled, checkUpdate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentVersion,
|
||||||
|
updateAvailable,
|
||||||
|
updateInfo,
|
||||||
|
isChecking,
|
||||||
|
isDownloading,
|
||||||
|
downloadProgress,
|
||||||
|
error,
|
||||||
|
checkUpdate,
|
||||||
|
installUpdate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+4
-1
@@ -6,7 +6,9 @@
|
|||||||
"dev": "next dev --webpack",
|
"dev": "next dev --webpack",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint",
|
||||||
|
"generate-changelog": "node scripts/generate-changelog.js",
|
||||||
|
"bump-version": "node scripts/bump-version.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tauri-apps/api": "^2.9.0",
|
"@tauri-apps/api": "^2.9.0",
|
||||||
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
Generated
+10
@@ -50,6 +50,9 @@ importers:
|
|||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
specifier: ^2.9.0
|
specifier: ^2.9.0
|
||||||
version: 2.9.0
|
version: 2.9.0
|
||||||
|
'@tauri-apps/plugin-updater':
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.9.0
|
||||||
'@types/react-syntax-highlighter':
|
'@types/react-syntax-highlighter':
|
||||||
specifier: ^15.5.13
|
specifier: ^15.5.13
|
||||||
version: 15.5.13
|
version: 15.5.13
|
||||||
@@ -1153,6 +1156,9 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@tauri-apps/[email protected]':
|
||||||
|
resolution: {integrity: sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg==}
|
||||||
|
|
||||||
'@tybys/[email protected]':
|
'@tybys/[email protected]':
|
||||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||||
|
|
||||||
@@ -4228,6 +4234,10 @@ snapshots:
|
|||||||
'@tauri-apps/cli-win32-ia32-msvc': 2.9.4
|
'@tauri-apps/cli-win32-ia32-msvc': 2.9.4
|
||||||
'@tauri-apps/cli-win32-x64-msvc': 2.9.4
|
'@tauri-apps/cli-win32-x64-msvc': 2.9.4
|
||||||
|
|
||||||
|
'@tauri-apps/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 2.9.0
|
||||||
|
|
||||||
'@tybys/[email protected]':
|
'@tybys/[email protected]':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
packages:
|
||||||
|
- '.'
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- sharp
|
- sharp
|
||||||
- unrs-resolver
|
- unrs-resolver
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
const versionType = process.argv[2] || 'patch'; // patch, minor, major
|
||||||
|
const shouldCommit = process.argv.includes('--commit');
|
||||||
|
|
||||||
|
// Validar tipo de versão
|
||||||
|
if (!['patch', 'minor', 'major'].includes(versionType)) {
|
||||||
|
console.error('❌ Tipo de versão inválido. Use: patch, minor ou major');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ler package.json
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||||
|
|
||||||
|
// Bump version
|
||||||
|
const [major, minor, patch] = packageJson.version.split('.').map(Number);
|
||||||
|
let newVersion;
|
||||||
|
|
||||||
|
switch (versionType) {
|
||||||
|
case 'major':
|
||||||
|
newVersion = `${major + 1}.0.0`;
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
newVersion = `${major}.${minor + 1}.0`;
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
default:
|
||||||
|
newVersion = `${major}.${minor}.${patch + 1}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📦 Atualizando versão de ${packageJson.version} para ${newVersion}...`);
|
||||||
|
|
||||||
|
// Atualizar package.json
|
||||||
|
packageJson.version = newVersion;
|
||||||
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||||
|
console.log('✅ package.json atualizado');
|
||||||
|
|
||||||
|
// Atualizar tauri.conf.json
|
||||||
|
const tauriConfPath = path.join(__dirname, '..', 'src-tauri', 'tauri.conf.json');
|
||||||
|
const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf-8'));
|
||||||
|
tauriConf.version = newVersion;
|
||||||
|
fs.writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2) + '\n');
|
||||||
|
console.log('✅ tauri.conf.json atualizado');
|
||||||
|
|
||||||
|
// Atualizar Cargo.toml
|
||||||
|
const cargoTomlPath = path.join(__dirname, '..', 'src-tauri', 'Cargo.toml');
|
||||||
|
let cargoToml = fs.readFileSync(cargoTomlPath, 'utf-8');
|
||||||
|
cargoToml = cargoToml.replace(/^version = ".*"$/m, `version = "${newVersion}"`);
|
||||||
|
fs.writeFileSync(cargoTomlPath, cargoToml);
|
||||||
|
console.log('✅ Cargo.toml atualizado');
|
||||||
|
|
||||||
|
// Criar commit e tag (opcional)
|
||||||
|
if (shouldCommit) {
|
||||||
|
try {
|
||||||
|
execSync(`git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml`, { stdio: 'inherit' });
|
||||||
|
execSync(`git commit -m "chore: bump version to ${newVersion}"`, { stdio: 'inherit' });
|
||||||
|
execSync(`git tag v${newVersion}`, { stdio: 'inherit' });
|
||||||
|
console.log(`✅ Commit e tag v${newVersion} criados`);
|
||||||
|
console.log(`\n📝 Próximo passo: git push && git push --tags`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erro ao criar commit/tag:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ Versão atualizada para ${newVersion}`);
|
||||||
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Ler versão do package.json
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||||
|
const currentVersion = packageJson.version;
|
||||||
|
|
||||||
|
// Verificar se estamos em um repositório Git
|
||||||
|
let isGitRepo = false;
|
||||||
|
try {
|
||||||
|
execSync('git rev-parse --git-dir', { stdio: 'ignore', cwd: path.join(__dirname, '..') });
|
||||||
|
isGitRepo = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Não é um repositório Git. Gerando changelog vazio.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para sanitizar mensagens de commit
|
||||||
|
function sanitizeMessage(message) {
|
||||||
|
return message
|
||||||
|
.replace(/\n/g, ' ')
|
||||||
|
.replace(/\r/g, '')
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para extrair tipo e mensagem do commit
|
||||||
|
function parseCommitMessage(fullMessage) {
|
||||||
|
const featMatch = fullMessage.match(/^feat(\(.+?\))?:\s*(.+)$/i);
|
||||||
|
const fixMatch = fullMessage.match(/^fix(\(.+?\))?:\s*(.+)$/i);
|
||||||
|
|
||||||
|
if (featMatch) {
|
||||||
|
return { type: 'feat', message: featMatch[2] || featMatch[0] };
|
||||||
|
}
|
||||||
|
if (fixMatch) {
|
||||||
|
return { type: 'fix', message: fixMatch[2] || fixMatch[0] };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para obter tags de versão
|
||||||
|
function getVersionTags() {
|
||||||
|
try {
|
||||||
|
const tags = execSync('git tag --sort=-version:refname', {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
cwd: path.join(__dirname, '..'),
|
||||||
|
})
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter(tag => /^v?\d+\.\d+\.\d+/.test(tag));
|
||||||
|
return tags;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para obter commits entre duas tags ou desde o início
|
||||||
|
function getCommitsBetweenTags(fromTag, toTag) {
|
||||||
|
try {
|
||||||
|
let range = '';
|
||||||
|
if (fromTag && toTag) {
|
||||||
|
range = `${fromTag}..${toTag}`;
|
||||||
|
} else if (toTag) {
|
||||||
|
range = toTag;
|
||||||
|
} else {
|
||||||
|
range = 'HEAD';
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFormat = '%H|%an|%ad|%s';
|
||||||
|
const dateFormat = '--date=iso-strict';
|
||||||
|
|
||||||
|
const output = execSync(
|
||||||
|
`git log ${range} --pretty=format:"${logFormat}" --date=${dateFormat}`,
|
||||||
|
{
|
||||||
|
encoding: 'utf-8',
|
||||||
|
cwd: path.join(__dirname, '..'),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return output
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(line => {
|
||||||
|
const [hash, author, date, message] = line.split('|');
|
||||||
|
const parsed = parseCommitMessage(message);
|
||||||
|
if (!parsed) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hash: hash.substring(0, 7),
|
||||||
|
type: parsed.type,
|
||||||
|
message: sanitizeMessage(parsed.message),
|
||||||
|
author: author.trim(),
|
||||||
|
date: date.trim(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função principal para gerar changelog
|
||||||
|
function generateChangelog() {
|
||||||
|
const changelogPath = path.join(__dirname, '..', 'data', 'changelog.json');
|
||||||
|
const dataDir = path.dirname(changelogPath);
|
||||||
|
|
||||||
|
// Criar diretório data se não existir
|
||||||
|
if (!fs.existsSync(dataDir)) {
|
||||||
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGitRepo) {
|
||||||
|
// Gerar changelog vazio se não for repositório Git
|
||||||
|
const emptyChangelog = {
|
||||||
|
version: currentVersion,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
releases: [],
|
||||||
|
};
|
||||||
|
fs.writeFileSync(changelogPath, JSON.stringify(emptyChangelog, null, 2), 'utf-8');
|
||||||
|
console.log('✅ Changelog vazio gerado (não é repositório Git)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const releases = [];
|
||||||
|
const versionTags = getVersionTags();
|
||||||
|
|
||||||
|
if (versionTags.length > 0) {
|
||||||
|
// Agrupar por tags de versão
|
||||||
|
for (let i = 0; i < versionTags.length; i++) {
|
||||||
|
const currentTag = versionTags[i];
|
||||||
|
const nextTag = i < versionTags.length - 1 ? versionTags[i + 1] : null;
|
||||||
|
|
||||||
|
const commits = getCommitsBetweenTags(nextTag, currentTag);
|
||||||
|
|
||||||
|
if (commits.length > 0) {
|
||||||
|
// Extrair versão da tag (remover 'v' prefix se existir)
|
||||||
|
const version = currentTag.replace(/^v/, '');
|
||||||
|
const releaseDate = commits[0]?.date || new Date().toISOString();
|
||||||
|
|
||||||
|
releases.push({
|
||||||
|
version,
|
||||||
|
date: releaseDate.split('T')[0],
|
||||||
|
commits,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adicionar commits não versionados (após a última tag)
|
||||||
|
const unreleasedCommits = getCommitsBetweenTags(versionTags[0], 'HEAD');
|
||||||
|
if (unreleasedCommits.length > 0) {
|
||||||
|
releases.unshift({
|
||||||
|
version: 'unreleased',
|
||||||
|
date: unreleasedCommits[0]?.date.split('T')[0] || new Date().toISOString().split('T')[0],
|
||||||
|
commits: unreleasedCommits,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Sem tags, agrupar por data (últimos 30 dias ou todos se menos)
|
||||||
|
const allCommits = getCommitsBetweenTags(null, 'HEAD');
|
||||||
|
|
||||||
|
if (allCommits.length > 0) {
|
||||||
|
// Agrupar por data
|
||||||
|
const commitsByDate = {};
|
||||||
|
allCommits.forEach(commit => {
|
||||||
|
const date = commit.date.split('T')[0];
|
||||||
|
if (!commitsByDate[date]) {
|
||||||
|
commitsByDate[date] = [];
|
||||||
|
}
|
||||||
|
commitsByDate[date].push(commit);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Criar releases por data (ordenado por data decrescente)
|
||||||
|
Object.keys(commitsByDate)
|
||||||
|
.sort((a, b) => b.localeCompare(a))
|
||||||
|
.forEach(date => {
|
||||||
|
releases.push({
|
||||||
|
version: 'unreleased',
|
||||||
|
date,
|
||||||
|
commits: commitsByDate[date],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelog = {
|
||||||
|
version: currentVersion,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
releases,
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(changelogPath, JSON.stringify(changelog, null, 2), 'utf-8');
|
||||||
|
console.log(`✅ Changelog gerado: ${releases.length} release(s) encontrada(s)`);
|
||||||
|
console.log(` Total de commits: ${releases.reduce((sum, r) => sum + r.commits.length, 0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executar
|
||||||
|
try {
|
||||||
|
generateChangelog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erro ao gerar changelog:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
Generated
+175
-1
@@ -129,6 +129,7 @@ dependencies = [
|
|||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-notification",
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
|
"tauri-plugin-updater",
|
||||||
"tokenizers",
|
"tokenizers",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-cron-scheduler",
|
"tokio-cron-scheduler",
|
||||||
@@ -136,7 +137,16 @@ dependencies = [
|
|||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"zip",
|
"zip 0.6.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1095,6 +1105,17 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.110",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
@@ -1822,8 +1843,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1833,9 +1856,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2280,6 +2305,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"webpki-roots 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2801,6 +2827,12 @@ dependencies = [
|
|||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-slab"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac"
|
name = "mac"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2938,6 +2970,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minisign-verify"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -3447,6 +3485,18 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-osa-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2 0.6.3",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -3655,6 +3705,20 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "osakit"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.6.3",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"objc2-osa-kit",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@@ -4129,6 +4193,61 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn"
|
||||||
|
version = "0.11.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"cfg_aliases",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"socket2 0.6.1",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-proto"
|
||||||
|
version = "0.11.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"lru-slab",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"ring",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"slab",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-udp"
|
||||||
|
version = "0.5.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"socket2 0.6.1",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.42"
|
version = "1.0.42"
|
||||||
@@ -4471,6 +4590,8 @@ dependencies = [
|
|||||||
"native-tls",
|
"native-tls",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"quinn",
|
||||||
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4478,6 +4599,7 @@ dependencies = [
|
|||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -4487,6 +4609,7 @@ dependencies = [
|
|||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-streams",
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
"webpki-roots 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4585,6 +4708,12 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -4637,6 +4766,7 @@ version = "1.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
|
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"web-time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5669,6 +5799,38 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-updater"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"dirs 6.0.0",
|
||||||
|
"flate2",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
|
"infer",
|
||||||
|
"log",
|
||||||
|
"minisign-verify",
|
||||||
|
"osakit",
|
||||||
|
"percent-encoding",
|
||||||
|
"reqwest 0.12.24",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tar",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zip 4.6.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
@@ -7668,6 +7830,18 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"indexmap 2.12.0",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.11.2+zstd.1.5.2"
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ futures-util = "0.3"
|
|||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
tauri-plugin-autostart = "2"
|
tauri-plugin-autostart = "2"
|
||||||
|
tauri-plugin-updater = "2"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
rfd = "0.14"
|
rfd = "0.14"
|
||||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"core:window:allow-maximize",
|
"core:window:allow-maximize",
|
||||||
"core:window:allow-unmaximize",
|
"core:window:allow-unmaximize",
|
||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
"core:window:allow-is-maximized"
|
"core:window:allow-is-maximized",
|
||||||
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -3613,6 +3613,9 @@ pub fn run() {
|
|||||||
// Plugin de notificações
|
// Plugin de notificações
|
||||||
app.handle().plugin(tauri_plugin_notification::init())?;
|
app.handle().plugin(tauri_plugin_notification::init())?;
|
||||||
|
|
||||||
|
// Plugin de atualização automática
|
||||||
|
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
|
||||||
|
|
||||||
// Modificar comportamento de fechar janela (ocultar ao invés de fechar)
|
// Modificar comportamento de fechar janela (ocultar ao invés de fechar)
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
let window_clone = window.clone();
|
let window_clone = window.clone();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"frontendDist": "../out",
|
"frontendDist": "../out",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"beforeBuildCommand": "pnpm build"
|
"beforeBuildCommand": "pnpm generate-changelog && pnpm build"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
@@ -35,5 +35,15 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"active": true,
|
||||||
|
"endpoints": [
|
||||||
|
"https://github.com/evandrodevbr/OllaHub/releases/latest/download/latest.json"
|
||||||
|
],
|
||||||
|
"dialog": true,
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDgxMjY3NThDMUQyREQ5QUYKUldTdjJTMGRqSFVtZ1k1ZWFHSEcxNExDZzgrNVh6M0Z6VXIyaTU5dDAvZnpQb3p0UVpuRW1Od3YK"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-1
@@ -56,6 +56,9 @@ export interface SettingsState {
|
|||||||
keywords: string[];
|
keywords: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Updates
|
||||||
|
autoCheckUpdates: boolean;
|
||||||
|
|
||||||
// Query Preprocessing
|
// Query Preprocessing
|
||||||
queryPreprocessing: {
|
queryPreprocessing: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -96,6 +99,9 @@ export interface SettingsState {
|
|||||||
addNotificationKeyword: (keyword: string) => void;
|
addNotificationKeyword: (keyword: string) => void;
|
||||||
removeNotificationKeyword: (keyword: string) => void;
|
removeNotificationKeyword: (keyword: string) => void;
|
||||||
|
|
||||||
|
// Updates Actions
|
||||||
|
setAutoCheckUpdates: (enabled: boolean) => void;
|
||||||
|
|
||||||
// Query Preprocessing Actions
|
// Query Preprocessing Actions
|
||||||
setQueryPreprocessingEnabled: (enabled: boolean) => void;
|
setQueryPreprocessingEnabled: (enabled: boolean) => void;
|
||||||
setQueryPreprocessingMinLength: (min: number) => void;
|
setQueryPreprocessingMinLength: (min: number) => void;
|
||||||
@@ -221,6 +227,7 @@ const initialState = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
keywords: [],
|
keywords: [],
|
||||||
},
|
},
|
||||||
|
autoCheckUpdates: true,
|
||||||
queryPreprocessing: {
|
queryPreprocessing: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
minLength: 3,
|
minLength: 3,
|
||||||
@@ -407,6 +414,10 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
// Updates Actions
|
||||||
|
setAutoCheckUpdates: (enabled) =>
|
||||||
|
set({ autoCheckUpdates: enabled }),
|
||||||
|
|
||||||
// Query Preprocessing Actions
|
// Query Preprocessing Actions
|
||||||
setQueryPreprocessingEnabled: (enabled) =>
|
setQueryPreprocessingEnabled: (enabled) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -479,7 +490,17 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'ollahub-settings',
|
name: 'ollahub-settings',
|
||||||
version: 1,
|
version: 3,
|
||||||
|
migrate: (persistedState: any, version: number) => {
|
||||||
|
// Migração da versão 1 para 3
|
||||||
|
if (version < 3) {
|
||||||
|
return {
|
||||||
|
...persistedState,
|
||||||
|
autoCheckUpdates: persistedState.autoCheckUpdates ?? true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return persistedState;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDgxMjY3NThDMUQyREQ5QUYKUldTdjJTMGRqSFVtZ1k1ZWFHSEcxNExDZzgrNVh6M0Z6VXIyaTU5dDAvZnpQb3p0UVpuRW1Od3YK
|
||||||
Reference in new issue
Block a user