// ======================================== // PromptHistoryPage Component // ======================================== // Prompt history page with timeline, stats, and AI insights import * as React from 'react'; import { useIntl } from 'react-intl'; import { RefreshCw, Search, Filter, AlertCircle, History, X, FolderOpen, } from 'lucide-react'; import { usePromptHistory, usePromptInsights, useInsightsHistory, usePromptHistoryMutations, useDeleteInsight, extractUniqueProjects, type PromptHistoryFilter, } from '@/hooks/usePromptHistory'; import { PromptStats, PromptStatsSkeleton } from '@/components/shared/PromptStats'; import { PromptCard } from '@/components/shared/PromptCard'; import { BatchOperationToolbar } from '@/components/shared/BatchOperationToolbar'; import { InsightsPanel } from '@/components/shared/InsightsPanel'; import { InsightsHistoryList } from '@/components/shared/InsightsHistoryList'; import { InsightDetailPanelOverlay } from '@/components/shared/InsightDetailPanel'; import { fetchInsightDetail } from '@/lib/api'; import type { InsightHistory } from '@/lib/api'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Badge } from '@/components/ui/Badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/Dialog'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, } from '@/components/ui/Dropdown'; import { cn } from '@/lib/utils'; type IntentFilter = 'all' | string; /** * PromptHistoryPage component - Main page for prompt history management */ export function PromptHistoryPage() { const { formatMessage } = useIntl(); // Filter state const [searchQuery, setSearchQuery] = React.useState(''); const [intentFilter, setIntentFilter] = React.useState('all'); const [projectFilter, setProjectFilter] = React.useState('all'); const [selectedTool, setSelectedTool] = React.useState<'gemini' | 'qwen' | 'codex'>('gemini'); // Dialog state const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); const [promptToDelete, setPromptToDelete] = React.useState(null); // Insight detail state const [selectedInsight, setSelectedInsight] = React.useState(null); const [insightDetailOpen, setInsightDetailOpen] = React.useState(false); // Batch operations state const [selectedPromptIds, setSelectedPromptIds] = React.useState>(new Set()); const [batchDeleteDialogOpen, setBatchDeleteDialogOpen] = React.useState(false); // Build filter object const filter: PromptHistoryFilter = React.useMemo( () => ({ search: searchQuery, intent: intentFilter === 'all' ? undefined : intentFilter, project: projectFilter === 'all' ? undefined : projectFilter, }), [searchQuery, intentFilter, projectFilter] ); // Fetch prompts and insights const { prompts, allPrompts, promptsBySession, stats, isLoading, isFetching, error, refetch, } = usePromptHistory({ filter }); const { data: insightsData, isLoading: insightsLoading } = usePromptInsights(); const { data: insightsHistoryData, isLoading: insightsHistoryLoading } = useInsightsHistory({ limit: 20 }); const { analyzePrompts, deletePrompt, batchDeletePrompts, isAnalyzing, isBatchDeleting } = usePromptHistoryMutations(); const { deleteInsight: deleteInsightMutation, isDeleting: isDeletingInsight } = useDeleteInsight(); const isMutating = isAnalyzing || isBatchDeleting; // Extract unique projects from all prompts const uniqueProjects = React.useMemo( () => extractUniqueProjects(allPrompts), [allPrompts] ); // Handlers const handleAnalyze = async () => { try { await analyzePrompts({ tool: selectedTool }); } catch (err) { console.error('Failed to analyze prompts:', err); } }; const handleDeleteClick = (promptId: string) => { setPromptToDelete(promptId); setDeleteDialogOpen(true); }; const handleConfirmDelete = async () => { if (!promptToDelete) return; try { await deletePrompt(promptToDelete); setDeleteDialogOpen(false); setPromptToDelete(null); } catch (err) { console.error('Failed to delete prompt:', err); } }; const handleClearSearch = () => { setSearchQuery(''); }; const handleInsightSelect = async (insightId: string) => { try { const insight = await fetchInsightDetail(insightId); setSelectedInsight(insight); setInsightDetailOpen(true); } catch (err) { console.error('Failed to fetch insight detail:', err); } }; const handleDeleteInsight = async (insightId: string) => { const locale = useIntl().locale; const confirmMessage = locale === 'zh' ? '确定要删除此分析吗?此操作无法撤销。' : 'Are you sure you want to delete this insight? This action cannot be undone.'; if (!window.confirm(confirmMessage)) { return; } try { await deleteInsightMutation(insightId); setInsightDetailOpen(false); setSelectedInsight(null); // Show success toast const successMessage = locale === 'zh' ? '洞察已删除' : 'Insight deleted'; if ((window as any).showToast) { (window as any).showToast(successMessage, 'success'); } } catch (err) { console.error('Failed to delete insight:', err); // Show error toast const errorMessage = locale === 'zh' ? '删除洞察失败' : 'Failed to delete insight'; if ((window as any).showToast) { (window as any).showToast(errorMessage, 'error'); } } }; const toggleIntentFilter = (intent: string) => { setIntentFilter((prev) => (prev === intent ? 'all' : intent)); }; const clearFilters = () => { setSearchQuery(''); setIntentFilter('all'); setProjectFilter('all'); }; // Batch operations handlers const handleSelectPrompt = (promptId: string, selected: boolean) => { setSelectedPromptIds((prev) => { const next = new Set(prev); if (selected) { next.add(promptId); } else { next.delete(promptId); } return next; }); }; const handleSelectAll = (selected: boolean) => { if (selected) { setSelectedPromptIds(new Set(prompts.map((p) => p.id))); } else { setSelectedPromptIds(new Set()); } }; const handleClearSelection = () => { setSelectedPromptIds(new Set()); }; const handleBatchDeleteClick = () => { if (selectedPromptIds.size > 0) { setBatchDeleteDialogOpen(true); } }; const handleConfirmBatchDelete = async () => { if (selectedPromptIds.size === 0) return; try { await batchDeletePrompts(Array.from(selectedPromptIds)); setBatchDeleteDialogOpen(false); setSelectedPromptIds(new Set()); } catch (err) { console.error('Failed to batch delete prompts:', err); } }; const hasActiveFilters = searchQuery.length > 0 || intentFilter !== 'all' || projectFilter !== 'all'; // Group prompts for timeline view const timelineGroups = React.useMemo(() => { const groups: Array<{ key: string; label: string; prompts: typeof prompts }> = []; // Group by session if available, otherwise by date const sessionKeys = Object.keys(promptsBySession); if (sessionKeys.length > 0 && sessionKeys.some((k) => k !== 'ungrouped')) { // Session-based grouping for (const [sessionKey, sessionPrompts] of Object.entries(promptsBySession)) { const filtered = sessionPrompts.filter((p) => prompts.some((fp) => fp.id === p.id) ); if (filtered.length > 0) { groups.push({ key: sessionKey, label: sessionKey === 'ungrouped' ? formatMessage({ id: 'prompts.timeline.ungrouped' }) : formatMessage({ id: 'prompts.timeline.session' }, { session: sessionKey }), prompts: filtered, }); } } } else { // Date-based grouping const dateGroups: Record = {}; for (const prompt of prompts) { const date = new Date(prompt.createdAt).toLocaleDateString(); if (!dateGroups[date]) { dateGroups[date] = []; } dateGroups[date].push(prompt); } for (const [date, datePrompts] of Object.entries(dateGroups)) { groups.push({ key: date, label: date, prompts: datePrompts }); } } return groups.sort((a, b) => { const aDate = a.prompts[0]?.createdAt ? new Date(a.prompts[0].createdAt).getTime() : 0; const bDate = b.prompts[0]?.createdAt ? new Date(b.prompts[0].createdAt).getTime() : 0; return bDate - aDate; }); }, [prompts, promptsBySession, formatMessage]); return (
{/* Header */}

{formatMessage({ id: 'prompts.title' })}

{formatMessage({ id: 'prompts.description' })}

{/* Error alert */} {error && (

{formatMessage({ id: 'common.errors.loadFailed' })}

{error.message}

)} {/* Stats */} {isLoading ? : } {/* Main content area with timeline and insights */}
{/* Timeline section */}
{/* Filters */}
{/* Search input */}
setSearchQuery(e.target.value)} className="pl-9 pr-9" /> {searchQuery && ( )}
{/* Intent filter dropdown */} {formatMessage({ id: 'prompts.filterByIntent' })} setIntentFilter('all')} className="justify-between" > {formatMessage({ id: 'prompts.intents.all' })} {intentFilter === 'all' && } {['bug-fix', 'feature', 'refactor', 'document', 'analyze'].map((intent) => ( toggleIntentFilter(intent)} className="justify-between" > {formatMessage({ id: `prompts.intents.${intent}` })} {intentFilter === intent && } ))} {formatMessage({ id: 'prompts.filterByProject' })} setProjectFilter('all')} className="justify-between" > {formatMessage({ id: 'prompts.projects.all' })} {projectFilter === 'all' && } {uniqueProjects.map((project) => ( setProjectFilter(project)} className="justify-between" > {project} {projectFilter === project && } ))} {hasActiveFilters && ( <> {formatMessage({ id: 'common.actions.clearFilters' })} )}
{/* Active filters display */} {hasActiveFilters && (
{formatMessage({ id: 'common.actions.filters' })}: {intentFilter !== 'all' && ( setIntentFilter('all')} > {formatMessage({ id: 'prompts.intents.intent' })}: {intentFilter} )} {projectFilter !== 'all' && ( setProjectFilter('all')} > {formatMessage({ id: 'prompts.projects.project' })}: {projectFilter} )} {searchQuery && ( {formatMessage({ id: 'common.actions.search' })}: {searchQuery} )}
)} {/* Batch operations toolbar */} 0 && selectedPromptIds.size === prompts.length} onSelectAll={handleSelectAll} onClearSelection={handleClearSelection} onDelete={handleBatchDeleteClick} isDeleting={isBatchDeleting} /> {/* Timeline */} {isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : timelineGroups.length === 0 ? (

{hasActiveFilters ? formatMessage({ id: 'prompts.emptyState.title' }) : formatMessage({ id: 'prompts.emptyState.noPrompts' })}

{hasActiveFilters ? formatMessage({ id: 'prompts.emptyState.message' }) : formatMessage({ id: 'prompts.emptyState.createFirst' })}

{hasActiveFilters && ( )}
) : (
{timelineGroups.map((group) => (
{/* Group header */}

{group.label}

{/* Prompt cards in group */}
{group.prompts.map((prompt) => ( 0 || prompts.some(p => selectedPromptIds.has(p.id))} /> ))}
))}
)}
{/* Insights panel */}
{/* Delete Confirmation Dialog */} {formatMessage({ id: 'prompts.dialog.deleteTitle' })} {formatMessage({ id: 'prompts.dialog.deleteConfirm' })} {/* Batch Delete Confirmation Dialog */} {formatMessage({ id: 'prompts.dialog.batchDeleteTitle' })} {formatMessage({ id: 'prompts.dialog.batchDeleteConfirm' }, { count: selectedPromptIds.size })} {/* Insight Detail Panel Overlay */} { setInsightDetailOpen(false); setSelectedInsight(null); }} onDelete={handleDeleteInsight} isDeleting={isDeletingInsight} showOverlay={true} />
); } export default PromptHistoryPage;