import { IDBPDatabase } from "idb"; import { StateUpdater, useRef, useState, Dispatch } from "preact/hooks"; import { ChatStore } from "@/types/chatstore"; import { MessageDetail } from "./chatgpt"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { Input } from "./components/ui/input"; interface ChatStoreSearchResult { key: IDBValidKey; cs: ChatStore; query: string; preview: string; } export default function Search(props: { db: Promise>; setSelectedChatIndex: Dispatch>; chatStore: ChatStore; show: boolean; setShow: (show: boolean) => void; }) { const [searchResult, setSearchResult] = useState([]); const [searching, setSearching] = useState(false); const [searchingNow, setSearchingNow] = useState(0); const [pageIndex, setPageIndex] = useState(0); const searchAbortRef = useRef(null); return ( Search Search messages by content.
{ const query = event.target.value.trim().toLowerCase(); if (!query) { setSearchResult([]); return; } // abort previous search if (searchAbortRef.current) { searchAbortRef.current.abort(); } // Create a new AbortController for the new operation const abortController = new AbortController(); searchAbortRef.current = abortController; const signal = abortController.signal; setSearching(true); const db = await props.db; const resultKeys = await db.getAllKeys("chatgpt-api-web"); const result: ChatStoreSearchResult[] = []; for (const key of resultKeys) { // abort the operation if the signal is set if (signal.aborted) { return; } const now = Math.floor( (result.length / resultKeys.length) * 100 ); if (now !== searchingNow) setSearchingNow(now); const value: ChatStore = await db.get("chatgpt-api-web", key); let preview: string = ""; for (const msg of value.history) { const contentType = typeof msg.content; if (contentType === "string") { if (!msg.content.includes(query)) continue; const beginIndex = msg.content.indexOf(query); preview = msg.content.slice( Math.max(0, beginIndex - 100), Math.min(msg.content.length, beginIndex + 239) ) as string; break; } else if (contentType === "object") { const details = msg.content as MessageDetail[]; for (const detail of details) { if (detail.type !== "text") continue; if (!detail.text?.includes(query)) continue; const beginIndex = detail.text.indexOf(query); preview = detail.text.slice( Math.max(0, beginIndex - 100), Math.min(detail.text.length, beginIndex + 239) ) as string; break; } } } if (preview === "") continue; result.push({ key, cs: value, query: query, preview: preview, }); } // sort by key desc result.sort((a, b) => { if (a.key < b.key) { return 1; } if (a.key > b.key) { return -1; } return 0; }); console.log(result); setPageIndex(0); setSearchResult(result); setSearching(false); }} />
{searching &&
Searching {searchingNow}%...
}
{searchResult .slice(pageIndex * 10, (pageIndex + 1) * 10) .map((result: ChatStoreSearchResult) => { return (
{ props.setSelectedChatIndex(parseInt(result.key.toString())); props.setShow(false); }} >
{result.key as number}
{result.preview}
); })}
{searchResult.length > 0 && ( { if (pageIndex === 0) return; setPageIndex(pageIndex - 1); }} // disabled={pageIndex === 0} /> {pageIndex + 1} of {Math.floor(searchResult.length / 10) + 1} { if (pageIndex === Math.floor(searchResult.length / 10)) return; setPageIndex(pageIndex + 1); }} // disabled={pageIndex === Math.floor(searchResult.length / 10)} /> )}
); }