| import { searchHistoryMaxItems, searchHistoryVersion } from '@/lib/constants' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export interface SearchHistoryItem { |
| label: string |
| lastAccessed: number |
| accessCount: number |
| } |
|
|
| export interface SearchHistoryData { |
| items: SearchHistoryItem[] |
| version: string |
| workspace?: string |
| } |
|
|
| export class SearchHistoryManager { |
| private static readonly STORAGE_KEY = 'lightrag_search_history' |
| private static readonly MAX_HISTORY = searchHistoryMaxItems |
| private static readonly VERSION = searchHistoryVersion |
|
|
| |
| |
| |
| |
| static getHistory(): SearchHistoryItem[] { |
| try { |
| const data = localStorage.getItem(this.STORAGE_KEY) |
| if (!data) return [] |
|
|
| const parsed: SearchHistoryData = JSON.parse(data) |
|
|
| |
| if (parsed.version !== this.VERSION) { |
| console.warn(`Search history version mismatch. Expected ${this.VERSION}, got ${parsed.version}. Clearing history.`) |
| this.clearHistory() |
| return [] |
| } |
|
|
| |
| if (!Array.isArray(parsed.items)) { |
| console.warn('Invalid search history format. Clearing history.') |
| this.clearHistory() |
| return [] |
| } |
|
|
| |
| return parsed.items.sort((a, b) => { |
| if (b.lastAccessed !== a.lastAccessed) { |
| return b.lastAccessed - a.lastAccessed |
| } |
| return (b.accessCount || 0) - (a.accessCount || 0) |
| }) |
| } catch (error) { |
| console.error('Error reading search history:', error) |
| this.clearHistory() |
| return [] |
| } |
| } |
|
|
| |
| |
| |
| |
| static addToHistory(label: string): void { |
| if (!label || typeof label !== 'string' || label.trim() === '') { |
| return |
| } |
|
|
| try { |
| const history = this.getHistory() |
| const now = Date.now() |
| const trimmedLabel = label.trim() |
|
|
| |
| const existingIndex = history.findIndex(item => item.label === trimmedLabel) |
|
|
| if (existingIndex >= 0) { |
| |
| const existingItem = history[existingIndex] |
| existingItem.lastAccessed = now |
| existingItem.accessCount = (existingItem.accessCount || 0) + 1 |
|
|
| |
| history.splice(existingIndex, 1) |
| history.unshift(existingItem) |
| } else { |
| |
| history.unshift({ |
| label: trimmedLabel, |
| lastAccessed: now, |
| accessCount: 1 |
| }) |
| } |
|
|
| |
| if (history.length > this.MAX_HISTORY) { |
| history.splice(this.MAX_HISTORY) |
| } |
|
|
| |
| const data: SearchHistoryData = { |
| items: history, |
| version: this.VERSION |
| } |
|
|
| localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)) |
| } catch (error) { |
| console.error('Error saving search history:', error) |
| } |
| } |
|
|
| |
| |
| |
| static clearHistory(): void { |
| try { |
| localStorage.removeItem(this.STORAGE_KEY) |
| } catch (error) { |
| console.error('Error clearing search history:', error) |
| } |
| } |
|
|
| |
| |
| |
| |
| static async initializeWithDefaults(popularLabels: string[]): Promise<void> { |
| const history = this.getHistory() |
|
|
| if (history.length === 0 && popularLabels.length > 0) { |
| try { |
| const now = Date.now() |
| const defaultItems: SearchHistoryItem[] = popularLabels.map((label, index) => ({ |
| label: label.trim(), |
| lastAccessed: now - index, |
| accessCount: 0 |
| })) |
|
|
| const data: SearchHistoryData = { |
| items: defaultItems, |
| version: this.VERSION |
| } |
|
|
| localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)) |
| } catch (error) { |
| console.error('Error initializing search history with defaults:', error) |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static getRecentSearches(limit: number = 10): SearchHistoryItem[] { |
| const history = this.getHistory() |
| return history |
| .filter(item => item.accessCount > 0) |
| .slice(0, limit) |
| } |
|
|
| |
| |
| |
| |
| |
| static getPopularRecommendations(limit?: number): SearchHistoryItem[] { |
| const history = this.getHistory() |
| const recommendations = history.filter(item => item.accessCount === 0) |
| return limit ? recommendations.slice(0, limit) : recommendations |
| } |
|
|
| |
| |
| |
| |
| |
| static getHistoryLabels(limit?: number): string[] { |
| const history = this.getHistory() |
| const labels = history.map(item => item.label) |
| return limit ? labels.slice(0, limit) : labels |
| } |
|
|
| |
| |
| |
| |
| |
| static hasLabel(label: string): boolean { |
| if (!label || typeof label !== 'string') return false |
| const history = this.getHistory() |
| return history.some(item => item.label === label.trim()) |
| } |
|
|
| |
| |
| |
| |
| static removeLabel(label: string): void { |
| if (!label || typeof label !== 'string') return |
|
|
| try { |
| const history = this.getHistory() |
| const trimmedLabel = label.trim() |
| const filteredHistory = history.filter(item => item.label !== trimmedLabel) |
|
|
| if (filteredHistory.length !== history.length) { |
| const data: SearchHistoryData = { |
| items: filteredHistory, |
| version: this.VERSION |
| } |
|
|
| localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)) |
| } |
| } catch (error) { |
| console.error('Error removing label from search history:', error) |
| } |
| } |
|
|
| |
| |
| |
| |
| static getStats(): { |
| totalItems: number |
| recentSearches: number |
| popularRecommendations: number |
| storageSize: number |
| } { |
| const history = this.getHistory() |
| const recentCount = history.filter(item => item.accessCount > 0).length |
| const popularCount = history.filter(item => item.accessCount === 0).length |
|
|
| let storageSize = 0 |
| try { |
| const data = localStorage.getItem(this.STORAGE_KEY) |
| storageSize = data ? data.length : 0 |
| } catch { |
| |
| } |
|
|
| return { |
| totalItems: history.length, |
| recentSearches: recentCount, |
| popularRecommendations: popularCount, |
| storageSize |
| } |
| } |
| } |
|
|