@voice-ai-workforce/react
Version:
React components with 3-tier interface modes for voice-controlled workforce applications
230 lines (229 loc) • 8.82 kB
JavaScript
// packages/react/src/hooks/useVoiceHistory.tsx
import { useState, useCallback, useEffect, useMemo } from 'react';
import { AIProvider } from '../../../types/src/types';
// Default options
const defaultOptions = {
maxHistoryItems: 100,
persistHistory: true,
storageKey: 'voice-ai-history',
enableStats: true,
};
export const useVoiceHistory = (options = {}) => {
const config = { ...defaultOptions, ...options };
// State
const [history, setHistory] = useState([]);
const [filters, setFiltersState] = useState({});
const [searchQuery, setSearchQuery] = useState('');
// Load history from localStorage on mount
useEffect(() => {
if (config.persistHistory && typeof window !== 'undefined') {
try {
const savedHistory = localStorage.getItem(config.storageKey);
if (savedHistory) {
const parsed = JSON.parse(savedHistory);
setHistory(parsed);
}
}
catch (error) {
console.warn('Failed to load voice history from localStorage:', error);
}
}
}, [config.persistHistory, config.storageKey]);
// Save history to localStorage when it changes
useEffect(() => {
if (config.persistHistory && typeof window !== 'undefined' && history.length > 0) {
try {
localStorage.setItem(config.storageKey, JSON.stringify(history));
}
catch (error) {
console.warn('Failed to save voice history to localStorage:', error);
}
}
}, [history, config.persistHistory, config.storageKey]);
// Add command to history
const addCommand = useCallback((command, response, duration) => {
const historyEntry = {
...command,
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
response,
duration,
success: response?.success ?? true,
error: response?.success === false ? 'Command failed' : undefined,
sessionId: `session-${Date.now()}`,
};
setHistory(prev => {
const newHistory = [historyEntry, ...prev];
// Limit history size
if (config.maxHistoryItems && newHistory.length > config.maxHistoryItems) {
return newHistory.slice(0, config.maxHistoryItems);
}
return newHistory;
});
}, [config.maxHistoryItems]);
// Clear all history
const clearHistory = useCallback(() => {
setHistory([]);
if (config.persistHistory && typeof window !== 'undefined') {
try {
localStorage.removeItem(config.storageKey);
}
catch (error) {
console.warn('Failed to clear voice history from localStorage:', error);
}
}
}, [config.persistHistory, config.storageKey]);
// Remove specific command
const removeCommand = useCallback((id) => {
setHistory(prev => prev.filter(entry => entry.id !== id));
}, []);
// Export history
const exportHistory = useCallback((format = 'json') => {
if (format === 'json') {
return JSON.stringify(history, null, 2);
}
// CSV export
const headers = ['timestamp', 'intent', 'rawText', 'confidence', 'provider', 'success', 'duration'];
const rows = history.map(entry => [
entry.timestamp.toISOString(),
entry.intent,
`"${entry.rawText.replace(/"/g, '""')}"`, // Escape quotes
entry.confidence.toString(),
entry.provider || 'unknown',
entry.success.toString(),
entry.duration?.toString() || '',
]);
return [headers.join(','), ...rows.map(row => row.join(','))].join('\n');
}, [history]);
// Set filters
const setFilters = useCallback((newFilters) => {
setFiltersState(prev => ({ ...prev, ...newFilters }));
}, []);
// Clear filters
const clearFilters = useCallback(() => {
setFiltersState({});
setSearchQuery('');
}, []);
// Replay command
const replayCommand = useCallback((id) => {
const entry = history.find(h => h.id === id);
if (!entry)
return null;
return {
intent: entry.intent,
entities: entry.entities,
confidence: entry.confidence,
rawText: entry.rawText,
timestamp: new Date(),
provider: entry.provider,
};
}, [history]);
// Get recent commands
const getRecentCommands = useCallback((count = 5) => {
return history.slice(0, count);
}, [history]);
// Get favorite commands (most used)
const getFavoriteCommands = useCallback((count = 5) => {
const intentCounts = history.reduce((acc, entry) => {
acc[entry.intent] = (acc[entry.intent] || 0) + 1;
return acc;
}, {});
const sortedIntents = Object.entries(intentCounts)
.sort(([, a], [, b]) => b - a)
.slice(0, count)
.map(([intent]) => intent);
return sortedIntents
.map(intent => history.find(entry => entry.intent === intent))
.filter(Boolean);
}, [history]);
// Filtered history based on search and filters
const filteredHistory = useMemo(() => {
let filtered = [...history];
// Apply search query
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
filtered = filtered.filter(entry => entry.intent.toLowerCase().includes(query) ||
entry.rawText.toLowerCase().includes(query) ||
(entry.response?.text || '').toLowerCase().includes(query));
}
// Apply filters
if (filters.dateFrom) {
filtered = filtered.filter(entry => entry.timestamp >= filters.dateFrom);
}
if (filters.dateTo) {
filtered = filtered.filter(entry => entry.timestamp <= filters.dateTo);
}
if (filters.commandType) {
filtered = filtered.filter(entry => entry.intent === filters.commandType);
}
if (filters.success !== undefined) {
filtered = filtered.filter(entry => entry.success === filters.success);
}
if (filters.provider) {
filtered = filtered.filter(entry => entry.provider === filters.provider);
}
return filtered;
}, [history, searchQuery, filters]);
// Calculate statistics
const stats = useMemo(() => {
if (!config.enableStats || history.length === 0) {
return {
totalCommands: 0,
successfulCommands: 0,
failedCommands: 0,
averageConfidence: 0,
commandsByProvider: {},
commandsByIntent: {},
mostUsedCommands: [],
averageResponseTime: 0,
};
}
const totalCommands = history.length;
const successfulCommands = history.filter(h => h.success).length;
const failedCommands = totalCommands - successfulCommands;
const averageConfidence = history.reduce((sum, h) => sum + h.confidence, 0) / totalCommands;
const commandsByProvider = history.reduce((acc, h) => {
const provider = h.provider || AIProvider.KEYWORDS;
acc[provider] = (acc[provider] || 0) + 1;
return acc;
}, {});
const commandsByIntent = history.reduce((acc, h) => {
acc[h.intent] = (acc[h.intent] || 0) + 1;
return acc;
}, {});
const mostUsedCommands = Object.entries(commandsByIntent)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([intent, count]) => ({ intent, count }));
const responseTimes = history.filter(h => h.duration).map(h => h.duration);
const averageResponseTime = responseTimes.length > 0
? responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length
: 0;
return {
totalCommands,
successfulCommands,
failedCommands,
averageConfidence,
commandsByProvider,
commandsByIntent,
mostUsedCommands,
averageResponseTime,
};
}, [history, config.enableStats]);
return {
history,
filteredHistory,
stats,
addCommand,
clearHistory,
removeCommand,
exportHistory,
filters,
setFilters,
clearFilters,
searchQuery,
setSearchQuery,
replayCommand,
getRecentCommands,
getFavoriteCommands,
};
};