Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c366eecb0 | ||
|
|
6cdc8d4294 |
No files matched your search
@@ -0,0 +1,185 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
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: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- 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: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- 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
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
~/.tauri/ollahub.key
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { AppUpdaterProvider } from "@/components/app-updater-provider";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -35,6 +36,7 @@ export default function RootLayout({
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
<AppUpdaterProvider />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+90
-1
@@ -24,11 +24,13 @@ import { useSettingsStore } from '@/store/settings-store';
|
||||
import { DomainTagsInput } from '@/components/settings/domain-tags-input';
|
||||
import { HardwareDashboard } from '@/components/settings/HardwareDashboard';
|
||||
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 { useRouter } from 'next/navigation';
|
||||
import { SourcesConfig, SourceCategory } from '@/lib/types';
|
||||
import { TitleBar } from '@/components/titlebar';
|
||||
import { useAppUpdater } from '@/hooks/use-app-updater';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
|
||||
interface Model {
|
||||
name: string;
|
||||
@@ -52,12 +54,20 @@ export default function SettingsPage() {
|
||||
const [isSavingSources, setIsSavingSources] = useState(false);
|
||||
const [recentLogs, setRecentLogs] = useState<string[]>([]);
|
||||
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
|
||||
const [appVersion, setAppVersion] = useState<string>('');
|
||||
|
||||
const updater = useAppUpdater();
|
||||
|
||||
// Load models, storage path, and sources config on mount
|
||||
useEffect(() => {
|
||||
loadModels();
|
||||
loadStoragePath();
|
||||
settings.fetchSources();
|
||||
|
||||
// Load app version
|
||||
getVersion()
|
||||
.then(setAppVersion)
|
||||
.catch(() => setAppVersion('0.1.0'));
|
||||
}, [settings.ollamaUrl]);
|
||||
|
||||
// Load logs periodically
|
||||
@@ -1095,6 +1105,85 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</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>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -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",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"generate-changelog": "node scripts/generate-changelog.js",
|
||||
"bump-version": "node scripts/bump-version.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
@@ -23,6 +25,7 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
Generated
+10
@@ -50,6 +50,9 @@ importers:
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.9.0
|
||||
version: 2.9.0
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: ^2.0.0
|
||||
version: 2.9.0
|
||||
'@types/react-syntax-highlighter':
|
||||
specifier: ^15.5.13
|
||||
version: 15.5.13
|
||||
@@ -1153,6 +1156,9 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/[email protected]':
|
||||
resolution: {integrity: sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg==}
|
||||
|
||||
'@tybys/[email protected]':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
@@ -4228,6 +4234,10 @@ snapshots:
|
||||
'@tauri-apps/cli-win32-ia32-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]':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -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-notification",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-updater",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
"tokio-cron-scheduler",
|
||||
@@ -136,7 +137,16 @@ dependencies = [
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"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]]
|
||||
@@ -1095,6 +1105,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
@@ -1822,8 +1843,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1833,9 +1856,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2280,6 +2305,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2801,6 +2827,12 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -2938,6 +2970,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -3447,6 +3485,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
@@ -3655,6 +3705,20 @@ dependencies = [
|
||||
"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]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -4129,6 +4193,61 @@ dependencies = [
|
||||
"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]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
@@ -4471,6 +4590,8 @@ dependencies = [
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4478,6 +4599,7 @@ dependencies = [
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -4487,6 +4609,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4585,6 +4708,12 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -4637,6 +4766,7 @@ version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -5669,6 +5799,38 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.9.1"
|
||||
@@ -7668,6 +7830,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
|
||||
@@ -44,6 +44,7 @@ futures-util = "0.3"
|
||||
regex = "1.10"
|
||||
walkdir = "2.5"
|
||||
tauri-plugin-autostart = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
dirs = "5.0"
|
||||
rfd = "0.14"
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"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
|
||||
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)
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let window_clone = window.clone();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"frontendDist": "../out",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build"
|
||||
"beforeBuildCommand": "pnpm generate-changelog && pnpm build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
@@ -35,5 +35,15 @@
|
||||
"icons/icon.icns",
|
||||
"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[];
|
||||
};
|
||||
|
||||
// Updates
|
||||
autoCheckUpdates: boolean;
|
||||
|
||||
// Query Preprocessing
|
||||
queryPreprocessing: {
|
||||
enabled: boolean;
|
||||
@@ -96,6 +99,9 @@ export interface SettingsState {
|
||||
addNotificationKeyword: (keyword: string) => void;
|
||||
removeNotificationKeyword: (keyword: string) => void;
|
||||
|
||||
// Updates Actions
|
||||
setAutoCheckUpdates: (enabled: boolean) => void;
|
||||
|
||||
// Query Preprocessing Actions
|
||||
setQueryPreprocessingEnabled: (enabled: boolean) => void;
|
||||
setQueryPreprocessingMinLength: (min: number) => void;
|
||||
@@ -221,6 +227,7 @@ const initialState = {
|
||||
enabled: true,
|
||||
keywords: [],
|
||||
},
|
||||
autoCheckUpdates: true,
|
||||
queryPreprocessing: {
|
||||
enabled: true,
|
||||
minLength: 3,
|
||||
@@ -407,6 +414,10 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
},
|
||||
})),
|
||||
|
||||
// Updates Actions
|
||||
setAutoCheckUpdates: (enabled) =>
|
||||
set({ autoCheckUpdates: enabled }),
|
||||
|
||||
// Query Preprocessing Actions
|
||||
setQueryPreprocessingEnabled: (enabled) =>
|
||||
set((state) => ({
|
||||
@@ -479,7 +490,17 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
}),
|
||||
{
|
||||
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