You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			190 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
		
		
			
		
	
	
			190 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
| 
											9 months ago
										 | import Fuse from "fuse.js"; | ||
|  | import { nanoid } from "nanoid"; | ||
|  | import { StoreKey } from "../constant"; | ||
|  | import { getLang } from "../locales"; | ||
|  | import { createPersistStore } from "../utils/store"; | ||
|  | 
 | ||
|  | export interface Prompt { | ||
|  |   id: string; | ||
|  |   isUser?: boolean; | ||
|  |   title: string; | ||
|  |   content: string; | ||
|  |   createdAt: number; | ||
|  | } | ||
|  | 
 | ||
|  | export const SearchService = { | ||
|  |   ready: false, | ||
|  |   builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }), | ||
|  |   userEngine: new Fuse<Prompt>([], { keys: ["title"] }), | ||
|  |   count: { | ||
|  |     builtin: 0, | ||
|  |   }, | ||
|  |   allPrompts: [] as Prompt[], | ||
|  |   builtinPrompts: [] as Prompt[], | ||
|  | 
 | ||
|  |   init(builtinPrompts: Prompt[], userPrompts: Prompt[]) { | ||
|  |     if (this.ready) { | ||
|  |       return; | ||
|  |     } | ||
|  |     this.allPrompts = userPrompts.concat(builtinPrompts); | ||
|  |     this.builtinPrompts = builtinPrompts.slice(); | ||
|  |     this.builtinEngine.setCollection(builtinPrompts); | ||
|  |     this.userEngine.setCollection(userPrompts); | ||
|  |     this.ready = true; | ||
|  |   }, | ||
|  | 
 | ||
|  |   remove(id: string) { | ||
|  |     this.userEngine.remove((doc) => doc.id === id); | ||
|  |   }, | ||
|  | 
 | ||
|  |   add(prompt: Prompt) { | ||
|  |     this.userEngine.add(prompt); | ||
|  |   }, | ||
|  | 
 | ||
|  |   search(text: string) { | ||
|  |     const userResults = this.userEngine.search(text); | ||
|  |     const builtinResults = this.builtinEngine.search(text); | ||
|  |     return userResults.concat(builtinResults).map((v) => v.item); | ||
|  |   }, | ||
|  | }; | ||
|  | 
 | ||
|  | export const usePromptStore = createPersistStore( | ||
|  |   { | ||
|  |     counter: 0, | ||
|  |     prompts: {} as Record<string, Prompt>, | ||
|  |   }, | ||
|  | 
 | ||
|  |   (set, get) => ({ | ||
|  |     add(prompt: Prompt) { | ||
|  |       const prompts = get().prompts; | ||
|  |       prompt.id = nanoid(); | ||
|  |       prompt.isUser = true; | ||
|  |       prompt.createdAt = Date.now(); | ||
|  |       prompts[prompt.id] = prompt; | ||
|  | 
 | ||
|  |       set(() => ({ | ||
|  |         prompts: prompts, | ||
|  |       })); | ||
|  | 
 | ||
|  |       return prompt.id!; | ||
|  |     }, | ||
|  | 
 | ||
|  |     get(id: string) { | ||
|  |       const targetPrompt = get().prompts[id]; | ||
|  | 
 | ||
|  |       if (!targetPrompt) { | ||
|  |         return SearchService.builtinPrompts.find((v) => v.id === id); | ||
|  |       } | ||
|  | 
 | ||
|  |       return targetPrompt; | ||
|  |     }, | ||
|  | 
 | ||
|  |     remove(id: string) { | ||
|  |       const prompts = get().prompts; | ||
|  |       delete prompts[id]; | ||
|  | 
 | ||
|  |       Object.entries(prompts).some(([key, prompt]) => { | ||
|  |         if (prompt.id === id) { | ||
|  |           delete prompts[key]; | ||
|  |           return true; | ||
|  |         } | ||
|  |         return false; | ||
|  |       }); | ||
|  | 
 | ||
|  |       SearchService.remove(id); | ||
|  | 
 | ||
|  |       set(() => ({ | ||
|  |         prompts, | ||
|  |         counter: get().counter + 1, | ||
|  |       })); | ||
|  |     }, | ||
|  | 
 | ||
|  |     getUserPrompts() { | ||
|  |       const userPrompts = Object.values(get().prompts ?? {}); | ||
|  |       userPrompts.sort((a, b) => | ||
|  |         b.id && a.id ? b.createdAt - a.createdAt : 0, | ||
|  |       ); | ||
|  |       return userPrompts; | ||
|  |     }, | ||
|  | 
 | ||
|  |     updatePrompt(id: string, updater: (prompt: Prompt) => void) { | ||
|  |       const prompt = get().prompts[id] ?? { | ||
|  |         title: "", | ||
|  |         content: "", | ||
|  |         id, | ||
|  |       }; | ||
|  | 
 | ||
|  |       SearchService.remove(id); | ||
|  |       updater(prompt); | ||
|  |       const prompts = get().prompts; | ||
|  |       prompts[id] = prompt; | ||
|  |       set(() => ({ prompts })); | ||
|  |       SearchService.add(prompt); | ||
|  |     }, | ||
|  | 
 | ||
|  |     search(text: string) { | ||
|  |       if (text.length === 0) { | ||
|  |         // return all rompts
 | ||
|  |         return this.getUserPrompts().concat(SearchService.builtinPrompts); | ||
|  |       } | ||
|  |       return SearchService.search(text) as Prompt[]; | ||
|  |     }, | ||
|  |   }), | ||
|  |   { | ||
|  |     name: StoreKey.Prompt, | ||
|  |     version: 3, | ||
|  | 
 | ||
|  |     migrate(state, version) { | ||
|  |       const newState = JSON.parse(JSON.stringify(state)) as { | ||
|  |         prompts: Record<string, Prompt>; | ||
|  |       }; | ||
|  | 
 | ||
|  |       if (version < 3) { | ||
|  |         Object.values(newState.prompts).forEach((p) => (p.id = nanoid())); | ||
|  |       } | ||
|  | 
 | ||
|  |       return newState as any; | ||
|  |     }, | ||
|  | 
 | ||
|  |     onRehydrateStorage(state) { | ||
|  |       // Skip store rehydration on server side
 | ||
|  |       if (typeof window === "undefined") { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       const PROMPT_URL = "./prompts.json"; | ||
|  | 
 | ||
|  |       type PromptList = Array<[string, string]>; | ||
|  | 
 | ||
|  |       fetch(PROMPT_URL) | ||
|  |         .then((res) => res.json()) | ||
|  |         .then((res) => { | ||
|  |           let fetchPrompts = [res.en, res.tw, res.cn]; | ||
|  |           if (getLang() === "cn") { | ||
|  |             fetchPrompts = fetchPrompts.reverse(); | ||
|  |           } | ||
|  |           const builtinPrompts = fetchPrompts.map((promptList: PromptList) => { | ||
|  |             return promptList.map( | ||
|  |               ([title, content]) => | ||
|  |                 ({ | ||
|  |                   id: nanoid(), | ||
|  |                   title, | ||
|  |                   content, | ||
|  |                   createdAt: Date.now(), | ||
|  |                 }) as Prompt, | ||
|  |             ); | ||
|  |           }); | ||
|  | 
 | ||
|  |           const userPrompts = usePromptStore.getState().getUserPrompts() ?? []; | ||
|  | 
 | ||
|  |           const allPromptsForSearch = builtinPrompts | ||
|  |             .reduce((pre, cur) => pre.concat(cur), []) | ||
|  |             .filter((v) => !!v.title && !!v.content); | ||
|  |           SearchService.count.builtin = | ||
|  |             res.en.length + res.cn.length + res.tw.length; | ||
|  |           SearchService.init(allPromptsForSearch, userPrompts); | ||
|  |         }); | ||
|  |     }, | ||
|  |   }, | ||
|  | ); |