Files
OllaHub/store/settings-store.ts
Evandro Fonseca Junior 6c366eecb0 feat: integrate automatic update checks and enhance settings UI
- Added functionality for automatic update checks in the settings page, allowing users to enable or disable this feature.
- Integrated app version display and update availability notifications.
- Updated settings store to manage auto-check updates state and handle version migration.
- Enhanced layout with a new card for update settings, improving user experience and accessibility.
2025-11-27 22:47:48 -03:00

508 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { SourcesConfig } from '@/lib/types';
export interface SearchCategory {
id: string;
name: string;
baseSites: string[];
enabled: boolean;
}
export interface SettingsState {
// AI & Models
ollamaUrl: string;
selectedModel: string;
selectedGpu: string | null;
systemPrompt: string;
contextWindow: number;
// Web Search
webSearch: {
enabled: boolean;
maxResults: number;
timeout: number;
excludedDomains: string[];
maxConcurrentTabs: number;
totalSourcesLimit: number;
categories: SearchCategory[];
userCustomSites: string[];
engineOrder: string[]; // Ordem dos motores de busca: ['google', 'bing', 'yahoo', 'duckduckgo', 'startpage']
minResultsPerEngine: number; // Mínimo de resultados para considerar sucesso
enableSemanticExpansion: boolean; // Habilitar expansão semântica de queries
semanticExpansionLanguage: string; // Idioma para expansão: 'pt-BR', 'en', 'es'
};
// Content Processing
contentProcessing: {
enabled: boolean;
autoSummarize: boolean;
chunkingEnabled: boolean;
maxChunkSize: number;
minRelevanceScore: number;
useKeyFactsExtraction: boolean;
fallbackToSummarization: boolean;
};
// Sources Config (from Rust backend)
sourcesConfig: SourcesConfig | null;
// AutoStart
autoStart: boolean;
// Notifications
notifications: {
enabled: boolean;
keywords: string[];
};
// Updates
autoCheckUpdates: boolean;
// Query Preprocessing
queryPreprocessing: {
enabled: boolean;
minLength: number;
maxLength: number;
autoSplitQuestions: boolean;
irrelevantPatterns: string[];
};
// Actions
setOllamaUrl: (url: string) => void;
setSelectedModel: (model: string) => void;
setSelectedGpu: (gpuId: string) => void;
setSystemPrompt: (prompt: string) => void;
setContextWindow: (ctx: number) => void;
setWebSearchEnabled: (enabled: boolean) => void;
setWebSearchMaxResults: (max: number) => void;
setWebSearchTimeout: (timeout: number) => void;
setWebSearchMaxConcurrentTabs: (max: number) => void;
setWebSearchTotalSourcesLimit: (limit: number) => void;
setWebSearchEngineOrder: (order: string[]) => void;
setWebSearchMinResultsPerEngine: (min: number) => void;
setWebSearchSemanticExpansion: (enabled: boolean) => void;
setWebSearchSemanticExpansionLanguage: (language: string) => void;
addExcludedDomain: (domain: string) => void;
removeExcludedDomain: (domain: string) => void;
toggleCategory: (categoryId: string) => void;
updateCategory: (category: SearchCategory) => void;
addCustomSite: (site: string) => void;
removeCustomSite: (site: string) => void;
resetSettings: () => void;
// New Actions for Sources & AutoStart
fetchSources: () => Promise<void>;
saveSources: (config: SourcesConfig) => Promise<void>;
toggleAutoStart: () => Promise<void>;
setNotificationsEnabled: (enabled: boolean) => void;
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;
setQueryPreprocessingMaxLength: (max: number) => void;
setQueryPreprocessingAutoSplit: (autoSplit: boolean) => void;
addIrrelevantPattern: (pattern: string) => void;
removeIrrelevantPattern: (pattern: string) => void;
// Content Processing Actions
setContentProcessingEnabled: (enabled: boolean) => void;
setContentProcessingAutoSummarize: (enabled: boolean) => void;
setContentProcessingChunkingEnabled: (enabled: boolean) => void;
setContentProcessingMaxChunkSize: (size: number) => void;
setContentProcessingMinRelevanceScore: (score: number) => void;
setContentProcessingUseKeyFactsExtraction: (enabled: boolean) => void;
setContentProcessingFallbackToSummarization: (enabled: boolean) => void;
}
const defaultSystemPrompt = `Você é um assistente de IA local integrado ao OllaHub.
## REGRAS ABSOLUTAS
1. **Nunca** escreva tags \`<metadata>\`, JSON oculto ou textos como "Metadados:". Toda resposta deve ser apenas texto visível ao usuário.
2. Use Markdown limpo: títulos, listas e blocos de código com linguagem indicada.
3. Seja direto, técnico e objetivo. Resumos iniciais são bem-vindos em respostas longas.
4. Comandos e códigos devem estar em blocos triplos (\`\`\`bash, \`\`\`json, etc.).
5. Se receber um bloco [CONTEXTO WEB], use essas informações prioritariamente para responder.
## CONTEXTO WEB
- Quando informações da web forem fornecidas, use-as como fonte principal.
- NÃO cite fontes no meio do texto. Use as informações do contexto web naturalmente, sem mencionar [1], [2], [3] ou outras referências numéricas.
- Se o contexto não for suficiente, diga isso claramente.
## FORMATO SUGERIDO
- Resumo inicial (1–2 frases) quando necessário.
- Seções \`##\` para organizar o conteúdo.
- Listas para passos/itens.
- Blocos de código com linguagem específica.
## ESTILO
- Cite limitações, hipóteses e próximos passos.
- Inclua links apenas quando essencial.
- Respostas sempre limpas, sem metadados ou conteúdo oculto.`;
// Categorias padrão curadas
const defaultCategories: SearchCategory[] = [
{
id: 'academic',
name: 'Acadêmico',
baseSites: [
'scielo.br',
'arxiv.org',
'scholar.google.com',
'pubmed.ncbi.nlm.nih.gov',
'researchgate.net',
],
enabled: false,
},
{
id: 'news',
name: 'Notícias',
baseSites: [
'g1.globo.com',
'bbc.com',
'cnnbrasil.com.br',
'folha.uol.com.br',
'estadao.com.br',
],
enabled: true,
},
{
id: 'tech',
name: 'Tech & Dev',
baseSites: [
'github.com',
'stackoverflow.com',
'dev.to',
'tabnews.com.br',
'medium.com',
],
enabled: true,
},
{
id: 'finance',
name: 'Financeiro',
baseSites: [
'infomoney.com.br',
'bloomberg.com',
'valor.globo.com',
'investing.com',
'yahoo.com/finance',
],
enabled: false,
},
];
const initialState = {
ollamaUrl: 'http://localhost:11434',
selectedModel: '',
selectedGpu: null,
systemPrompt: defaultSystemPrompt,
contextWindow: 4096,
webSearch: {
enabled: true,
maxResults: 10,
timeout: 15000, // Timeout em milissegundos (15 segundos)
excludedDomains: ['youtube.com', 'linkedin.com'],
maxConcurrentTabs: 5,
totalSourcesLimit: 100,
categories: defaultCategories,
userCustomSites: [],
engineOrder: ['google', 'bing', 'yahoo', 'duckduckgo', 'startpage'], // Ordem padrão
minResultsPerEngine: 1, // Mínimo de 1 resultado para considerar sucesso
enableSemanticExpansion: true, // Habilitado por padrão
semanticExpansionLanguage: 'pt-BR', // Português brasileiro por padrão
},
sourcesConfig: null,
autoStart: false,
notifications: {
enabled: true,
keywords: [],
},
autoCheckUpdates: true,
queryPreprocessing: {
enabled: true,
minLength: 3,
maxLength: 2000,
autoSplitQuestions: true,
irrelevantPatterns: [
'oi',
'olá',
'ola',
'teste',
'test',
'hello',
'hi',
'hey',
],
},
contentProcessing: {
enabled: true,
autoSummarize: true,
chunkingEnabled: true,
maxChunkSize: 1024,
minRelevanceScore: 0.1,
useKeyFactsExtraction: true,
fallbackToSummarization: true,
},
};
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
...initialState,
setOllamaUrl: (url) => set({ ollamaUrl: url }),
setSelectedModel: (model) => set({ selectedModel: model }),
setSelectedGpu: (gpuId) => set({ selectedGpu: gpuId }),
setSystemPrompt: (prompt) => set({ systemPrompt: prompt }),
setContextWindow: (ctx) => set({ contextWindow: ctx }),
setWebSearchEnabled: (enabled) =>
set((state) => ({
webSearch: { ...state.webSearch, enabled },
})),
setWebSearchMaxResults: (max) =>
set((state) => ({
webSearch: { ...state.webSearch, maxResults: max },
})),
setWebSearchTimeout: (timeout) =>
set((state) => ({
webSearch: { ...state.webSearch, timeout },
})),
setWebSearchMaxConcurrentTabs: (max) =>
set((state) => ({
webSearch: { ...state.webSearch, maxConcurrentTabs: max },
})),
setWebSearchTotalSourcesLimit: (limit) =>
set((state) => ({
webSearch: { ...state.webSearch, totalSourcesLimit: limit },
})),
setWebSearchEngineOrder: (order) =>
set((state) => ({
webSearch: { ...state.webSearch, engineOrder: order },
})),
setWebSearchMinResultsPerEngine: (min) =>
set((state) => ({
webSearch: { ...state.webSearch, minResultsPerEngine: min },
})),
setWebSearchSemanticExpansion: (enabled) =>
set((state) => ({
webSearch: { ...state.webSearch, enableSemanticExpansion: enabled },
})),
setWebSearchSemanticExpansionLanguage: (language) =>
set((state) => ({
webSearch: { ...state.webSearch, semanticExpansionLanguage: language },
})),
addExcludedDomain: (domain) =>
set((state) => {
const normalized = domain.toLowerCase().trim().replace(/^https?:\/\//, '').replace(/\/$/, '');
if (!normalized || state.webSearch.excludedDomains.includes(normalized)) {
return state;
}
return {
webSearch: {
...state.webSearch,
excludedDomains: [...state.webSearch.excludedDomains, normalized],
},
};
}),
removeExcludedDomain: (domain) =>
set((state) => ({
webSearch: {
...state.webSearch,
excludedDomains: state.webSearch.excludedDomains.filter((d) => d !== domain),
},
})),
toggleCategory: (categoryId) =>
set((state) => ({
webSearch: {
...state.webSearch,
categories: state.webSearch.categories.map((cat) =>
cat.id === categoryId ? { ...cat, enabled: !cat.enabled } : cat
),
},
})),
updateCategory: (category) =>
set((state) => ({
webSearch: {
...state.webSearch,
categories: state.webSearch.categories.map((cat) =>
cat.id === category.id ? category : cat
),
},
})),
addCustomSite: (site) =>
set((state) => {
const normalized = site.toLowerCase().trim().replace(/^https?:\/\//, '').replace(/\/$/, '');
if (!normalized || state.webSearch.userCustomSites.includes(normalized)) {
return state;
}
return {
webSearch: {
...state.webSearch,
userCustomSites: [...state.webSearch.userCustomSites, normalized],
},
};
}),
removeCustomSite: (site) =>
set((state) => ({
webSearch: {
...state.webSearch,
userCustomSites: state.webSearch.userCustomSites.filter((s) => s !== site),
},
})),
resetSettings: () => set(initialState),
// Sources Config Actions
fetchSources: async () => {
try {
const { invoke } = await import('@tauri-apps/api/core');
const config = await invoke<SourcesConfig>('load_sources_config_command');
set({ sourcesConfig: config });
} catch (error) {
console.error('Failed to fetch sources config:', error);
}
},
saveSources: async (config: SourcesConfig) => {
try {
const { invoke } = await import('@tauri-apps/api/core');
await invoke('save_sources_config_command', { config });
set({ sourcesConfig: config });
} catch (error) {
console.error('Failed to save sources config:', error);
throw error;
}
},
// AutoStart Actions
toggleAutoStart: async () => {
// TODO: Implementar quando tauri-plugin-autostart estiver configurado
set((state) => ({ autoStart: !state.autoStart }));
},
// Notifications Actions
setNotificationsEnabled: (enabled) =>
set((state) => ({
notifications: { ...state.notifications, enabled },
})),
addNotificationKeyword: (keyword) =>
set((state) => {
const normalized = keyword.toLowerCase().trim();
if (!normalized || state.notifications.keywords.includes(normalized)) {
return state;
}
return {
notifications: {
...state.notifications,
keywords: [...state.notifications.keywords, normalized],
},
};
}),
removeNotificationKeyword: (keyword) =>
set((state) => ({
notifications: {
...state.notifications,
keywords: state.notifications.keywords.filter((k) => k !== keyword),
},
})),
// Updates Actions
setAutoCheckUpdates: (enabled) =>
set({ autoCheckUpdates: enabled }),
// Query Preprocessing Actions
setQueryPreprocessingEnabled: (enabled) =>
set((state) => ({
queryPreprocessing: { ...state.queryPreprocessing, enabled },
})),
setQueryPreprocessingMinLength: (min) =>
set((state) => ({
queryPreprocessing: { ...state.queryPreprocessing, minLength: min },
})),
setQueryPreprocessingMaxLength: (max) =>
set((state) => ({
queryPreprocessing: { ...state.queryPreprocessing, maxLength: max },
})),
setQueryPreprocessingAutoSplit: (autoSplit) =>
set((state) => ({
queryPreprocessing: { ...state.queryPreprocessing, autoSplitQuestions: autoSplit },
})),
addIrrelevantPattern: (pattern) =>
set((state) => {
const normalized = pattern.toLowerCase().trim();
if (!normalized || state.queryPreprocessing.irrelevantPatterns.includes(normalized)) {
return state;
}
return {
queryPreprocessing: {
...state.queryPreprocessing,
irrelevantPatterns: [...state.queryPreprocessing.irrelevantPatterns, normalized],
},
};
}),
removeIrrelevantPattern: (pattern) =>
set((state) => ({
queryPreprocessing: {
...state.queryPreprocessing,
irrelevantPatterns: state.queryPreprocessing.irrelevantPatterns.filter(
(p) => p !== pattern.toLowerCase().trim()
),
},
})),
// Content Processing Actions
setContentProcessingEnabled: (enabled) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, enabled },
})),
setContentProcessingAutoSummarize: (enabled) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, autoSummarize: enabled },
})),
setContentProcessingChunkingEnabled: (enabled) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, chunkingEnabled: enabled },
})),
setContentProcessingMaxChunkSize: (size) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, maxChunkSize: size },
})),
setContentProcessingMinRelevanceScore: (score) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, minRelevanceScore: score },
})),
setContentProcessingUseKeyFactsExtraction: (enabled) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, useKeyFactsExtraction: enabled },
})),
setContentProcessingFallbackToSummarization: (enabled) =>
set((state) => ({
contentProcessing: { ...state.contentProcessing, fallbackToSummarization: enabled },
})),
}),
{
name: 'ollahub-settings',
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;
},
}
)
);