import { VSCodeButton, VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react" import { useExtensionState } from "../context/ExtensionStateContext" import { vscode } from "../utils/vscode" import { Virtuoso } from "react-virtuoso" import { memo, useMemo, useState, useEffect } from "react" import Fuse, { FuseResult } from "fuse.js" type HistoryViewProps = { onDone: () => void } type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRelevant" const HistoryView = ({ onDone }: HistoryViewProps) => { const { taskHistory } = useExtensionState() const [searchQuery, setSearchQuery] = useState("") const [sortOption, setSortOption] = useState("newest") const [lastNonRelevantSort, setLastNonRelevantSort] = useState("newest") useEffect(() => { if (searchQuery && sortOption !== "mostRelevant" && !lastNonRelevantSort) { setLastNonRelevantSort(sortOption) setSortOption("mostRelevant") } else if (!searchQuery && sortOption === "mostRelevant" && lastNonRelevantSort) { setSortOption(lastNonRelevantSort) setLastNonRelevantSort(null) } }, [searchQuery, sortOption, lastNonRelevantSort]) const handleHistorySelect = (id: string) => { vscode.postMessage({ type: "showTaskWithId", text: id }) } const handleDeleteHistoryItem = (id: string) => { vscode.postMessage({ type: "deleteTaskWithId", text: id }) } const formatDate = (timestamp: number) => { const date = new Date(timestamp) return date ?.toLocaleString("en-US", { month: "long", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }) .replace(", ", " ") .replace(" at", ",") .toUpperCase() } const presentableTasks = useMemo(() => { return taskHistory.filter((item) => item.ts && item.task) }, [taskHistory]) const fuse = useMemo(() => { return new Fuse(presentableTasks, { keys: ["task"], threshold: 0.6, shouldSort: true, isCaseSensitive: false, ignoreLocation: false, includeMatches: true, minMatchCharLength: 1, }) }, [presentableTasks]) const taskHistorySearchResults = useMemo(() => { let results = searchQuery ? highlight(fuse.search(searchQuery)) : presentableTasks results.sort((a, b) => { switch (sortOption) { case "oldest": return a.ts - b.ts case "mostExpensive": return (b.totalCost || 0) - (a.totalCost || 0) case "mostTokens": return ( (b.tokensIn || 0) + (b.tokensOut || 0) + (b.cacheWrites || 0) + (b.cacheReads || 0) - ((a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0)) ) case "mostRelevant": return searchQuery ? 0 : b.ts - a.ts // Keep fuse order if searching, otherwise sort by newest case "newest": default: return b.ts - a.ts } }) return results }, [presentableTasks, searchQuery, fuse, sortOption]) return ( <>

History

Done
{ const newValue = (e.target as HTMLInputElement)?.value setSearchQuery(newValue) if (newValue && !searchQuery && sortOption !== "mostRelevant") { setLastNonRelevantSort(sortOption) setSortOption("mostRelevant") } }}>
{searchQuery && (
setSearchQuery("")} slot="end" style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100%", }}>
)}
setSortOption((e.target as HTMLInputElement).value as SortOption)}> Newest Oldest Most Expensive Most Tokens Most Relevant
{presentableTasks.length === 0 && (
No history found
)} (
handleHistorySelect(item.id)}>
{formatDate(item.ts)} { e.stopPropagation() handleDeleteHistoryItem(item.id) }} className="delete-button">
Tokens: {item.tokensIn?.toLocaleString()} {item.tokensOut?.toLocaleString()}
{!item.totalCost && }
{!!item.cacheWrites && (
Cache: +{item.cacheWrites?.toLocaleString()} {(item.cacheReads || 0).toLocaleString()}
)} {!!item.totalCost && (
API Cost: ${item.totalCost?.toFixed(4)}
)}
)} />
) } const ExportButton = ({ itemId }: { itemId: string }) => ( { e.stopPropagation() vscode.postMessage({ type: "exportTaskWithId", text: itemId }) }}>
EXPORT
) // https://gist.github.com/evenfrost/1ba123656ded32fb7a0cd4651efd4db0 const highlight = (fuseSearchResult: FuseResult[], highlightClassName: string = "history-item-highlight") => { const set = (obj: Record, path: string, value: any) => { const pathValue = path.split(".") let i: number for (i = 0; i < pathValue.length - 1; i++) { obj = obj[pathValue[i]] as Record } obj[pathValue[i]] = value } const generateHighlightedText = (inputText: string, regions: [number, number][] = []) => { let content = "" let nextUnhighlightedRegionStartingIndex = 0 regions.forEach((region) => { const lastRegionNextIndex = region[1] + 1 content += [ inputText.substring(nextUnhighlightedRegionStartingIndex, region[0]), ``, inputText.substring(region[0], lastRegionNextIndex), "", ].join("") nextUnhighlightedRegionStartingIndex = lastRegionNextIndex }) content += inputText.substring(nextUnhighlightedRegionStartingIndex) return content } return fuseSearchResult .filter(({ matches }) => matches && matches.length) .map(({ item, matches }) => { const highlightedItem = { ...item } matches?.forEach((match) => { if (match.key && typeof match.value === "string") { set(highlightedItem, match.key, generateHighlightedText(match.value, [...match.indices])) } }) return highlightedItem }) } export default memo(HistoryView)