mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	feat: #2 add prompt hints
This commit is contained in:
		@@ -333,11 +333,65 @@
 | 
			
		||||
 | 
			
		||||
.chat-input-panel {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 20px;
 | 
			
		||||
  bottom: 0px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin single-line {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompt-hints {
 | 
			
		||||
  min-height: 20px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-height: 50vh;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column-reverse;
 | 
			
		||||
 | 
			
		||||
  background-color: var(--white);
 | 
			
		||||
  border: var(--border-in-light);
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  box-shadow: var(--shadow);
 | 
			
		||||
 | 
			
		||||
  .prompt-hint {
 | 
			
		||||
    color: var(--black);
 | 
			
		||||
    padding: 6px 10px;
 | 
			
		||||
    animation: slide-in ease 0.3s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: all ease 0.3s;
 | 
			
		||||
    border: transparent 1px solid;
 | 
			
		||||
    margin: 4px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
 | 
			
		||||
    &:not(:last-child) {
 | 
			
		||||
      margin-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .hint-title {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      font-weight: bolder;
 | 
			
		||||
 | 
			
		||||
      @include single-line();
 | 
			
		||||
    }
 | 
			
		||||
    .hint-content {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
 | 
			
		||||
      @include single-line();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &-selected,
 | 
			
		||||
    &:hover {
 | 
			
		||||
      border-color: var(--primary);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-input-panel-inner {
 | 
			
		||||
@@ -375,7 +429,7 @@
 | 
			
		||||
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 30px;
 | 
			
		||||
  bottom: 10px;
 | 
			
		||||
  bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.export-content {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { useState, useRef, useEffect, useLayoutEffect } from "react";
 | 
			
		||||
import { useDebouncedCallback } from "use-debounce";
 | 
			
		||||
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
import styles from "./home.module.scss";
 | 
			
		||||
@@ -28,6 +29,7 @@ import Locale from "../locales";
 | 
			
		||||
import dynamic from "next/dynamic";
 | 
			
		||||
import { REPO_URL } from "../constant";
 | 
			
		||||
import { ControllerPool } from "../requests";
 | 
			
		||||
import { Prompt, usePromptStore } from "../store/prompt";
 | 
			
		||||
 | 
			
		||||
export function Loading(props: { noLogo?: boolean }) {
 | 
			
		||||
  return (
 | 
			
		||||
@@ -146,24 +148,77 @@ function useSubmitHandler() {
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function PromptHints(props: {
 | 
			
		||||
  prompts: Prompt[];
 | 
			
		||||
  onPromptSelect: (prompt: Prompt) => void;
 | 
			
		||||
}) {
 | 
			
		||||
  if (props.prompts.length === 0) return null;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles["prompt-hints"]}>
 | 
			
		||||
      {props.prompts.map((prompt, i) => (
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles["prompt-hint"]}
 | 
			
		||||
          key={prompt.title + i.toString()}
 | 
			
		||||
          onClick={() => props.onPromptSelect(prompt)}
 | 
			
		||||
        >
 | 
			
		||||
          <div className={styles["hint-title"]}>{prompt.title}</div>
 | 
			
		||||
          <div className={styles["hint-content"]}>{prompt.content}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ))}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Chat(props: { showSideBar?: () => void }) {
 | 
			
		||||
  type RenderMessage = Message & { preview?: boolean };
 | 
			
		||||
 | 
			
		||||
  const chatStore = useChatStore();
 | 
			
		||||
  const [session, sessionIndex] = useChatStore((state) => [
 | 
			
		||||
    state.currentSession(),
 | 
			
		||||
    state.currentSessionIndex,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const inputRef = useRef<HTMLTextAreaElement>(null);
 | 
			
		||||
  const [userInput, setUserInput] = useState("");
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
  const { submitKey, shouldSubmit } = useSubmitHandler();
 | 
			
		||||
 | 
			
		||||
  const onUserInput = useChatStore((state) => state.onUserInput);
 | 
			
		||||
  // prompt hints
 | 
			
		||||
  const promptStore = usePromptStore();
 | 
			
		||||
  const [promptHints, setPromptHints] = useState<Prompt[]>([]);
 | 
			
		||||
  const onSearch = useDebouncedCallback(
 | 
			
		||||
    (text: string) => {
 | 
			
		||||
      if (chatStore.config.disablePromptHint) return;
 | 
			
		||||
      setPromptHints(promptStore.search(text));
 | 
			
		||||
    },
 | 
			
		||||
    100,
 | 
			
		||||
    { leading: true, trailing: true }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const onPromptSelect = (prompt: Prompt) => {
 | 
			
		||||
    setUserInput(prompt.content);
 | 
			
		||||
    setPromptHints([]);
 | 
			
		||||
    inputRef.current?.focus();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // only search prompts when user input is short
 | 
			
		||||
  const SEARCH_TEXT_LIMIT = 10;
 | 
			
		||||
  const onInput = (text: string) => {
 | 
			
		||||
    setUserInput(text);
 | 
			
		||||
    const n = text.trim().length;
 | 
			
		||||
    if (n === 0 || n > SEARCH_TEXT_LIMIT) {
 | 
			
		||||
      setPromptHints([]);
 | 
			
		||||
    } else {
 | 
			
		||||
      onSearch(text);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // submit user input
 | 
			
		||||
  const onUserSubmit = () => {
 | 
			
		||||
    if (userInput.length <= 0) return;
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
    onUserInput(userInput).then(() => setIsLoading(false));
 | 
			
		||||
    chatStore.onUserInput(userInput).then(() => setIsLoading(false));
 | 
			
		||||
    setUserInput("");
 | 
			
		||||
    inputRef.current?.focus();
 | 
			
		||||
  };
 | 
			
		||||
@@ -198,7 +253,9 @@ export function Chat(props: { showSideBar?: () => void }) {
 | 
			
		||||
    for (let i = botIndex; i >= 0; i -= 1) {
 | 
			
		||||
      if (messages[i].role === "user") {
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        onUserInput(messages[i].content).then(() => setIsLoading(false));
 | 
			
		||||
        chatStore
 | 
			
		||||
          .onUserInput(messages[i].content)
 | 
			
		||||
          .then(() => setIsLoading(false));
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -206,7 +263,6 @@ export function Chat(props: { showSideBar?: () => void }) {
 | 
			
		||||
 | 
			
		||||
  // for auto-scroll
 | 
			
		||||
  const latestMessageRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const inputRef = useRef<HTMLTextAreaElement>(null);
 | 
			
		||||
 | 
			
		||||
  // wont scroll while hovering messages
 | 
			
		||||
  const [autoScroll, setAutoScroll] = useState(false);
 | 
			
		||||
@@ -373,17 +429,21 @@ export function Chat(props: { showSideBar?: () => void }) {
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className={styles["chat-input-panel"]}>
 | 
			
		||||
        <PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
 | 
			
		||||
        <div className={styles["chat-input-panel-inner"]}>
 | 
			
		||||
          <textarea
 | 
			
		||||
            ref={inputRef}
 | 
			
		||||
            className={styles["chat-input"]}
 | 
			
		||||
            placeholder={Locale.Chat.Input(submitKey)}
 | 
			
		||||
            rows={3}
 | 
			
		||||
            onInput={(e) => setUserInput(e.currentTarget.value)}
 | 
			
		||||
            rows={4}
 | 
			
		||||
            onInput={(e) => onInput(e.currentTarget.value)}
 | 
			
		||||
            value={userInput}
 | 
			
		||||
            onKeyDown={(e) => onInputKeyDown(e as any)}
 | 
			
		||||
            onFocus={() => setAutoScroll(true)}
 | 
			
		||||
            onBlur={() => setAutoScroll(false)}
 | 
			
		||||
            onBlur={() => {
 | 
			
		||||
              setAutoScroll(false);
 | 
			
		||||
              setTimeout(() => setPromptHints([]), 100);
 | 
			
		||||
            }}
 | 
			
		||||
            autoFocus
 | 
			
		||||
          />
 | 
			
		||||
          <IconButton
 | 
			
		||||
@@ -411,9 +471,11 @@ function useSwitchTheme() {
 | 
			
		||||
      document.body.classList.add("light");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const themeColor = getComputedStyle(document.body).getPropertyValue("--theme-color").trim();
 | 
			
		||||
    const themeColor = getComputedStyle(document.body)
 | 
			
		||||
      .getPropertyValue("--theme-color")
 | 
			
		||||
      .trim();
 | 
			
		||||
    const metaDescription = document.querySelector('meta[name="theme-color"]');
 | 
			
		||||
    metaDescription?.setAttribute('content', themeColor);
 | 
			
		||||
    metaDescription?.setAttribute("content", themeColor);
 | 
			
		||||
  }, [config.theme]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -566,7 +628,7 @@ export function Home() {
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<AddIcon />}
 | 
			
		||||
              text={Locale.Home.NewChat}
 | 
			
		||||
              onClick={()=>{
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                createNewSession();
 | 
			
		||||
                setShowSideBar(false);
 | 
			
		||||
              }}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,9 @@ import styles from "./settings.module.scss";
 | 
			
		||||
import ResetIcon from "../icons/reload.svg";
 | 
			
		||||
import CloseIcon from "../icons/close.svg";
 | 
			
		||||
import ClearIcon from "../icons/clear.svg";
 | 
			
		||||
import EditIcon from "../icons/edit.svg";
 | 
			
		||||
 | 
			
		||||
import { List, ListItem, Popover } from "./ui-lib";
 | 
			
		||||
import { List, ListItem, Popover, showToast } from "./ui-lib";
 | 
			
		||||
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
import {
 | 
			
		||||
@@ -19,12 +20,13 @@ import {
 | 
			
		||||
  useUpdateStore,
 | 
			
		||||
  useAccessStore,
 | 
			
		||||
} from "../store";
 | 
			
		||||
import { Avatar } from "./home";
 | 
			
		||||
import { Avatar, PromptHints } from "./home";
 | 
			
		||||
 | 
			
		||||
import Locale, { changeLang, getLang } from "../locales";
 | 
			
		||||
import { getCurrentCommitId } from "../utils";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { UPDATE_URL } from "../constant";
 | 
			
		||||
import { SearchService, usePromptStore } from "../store/prompt";
 | 
			
		||||
 | 
			
		||||
function SettingItem(props: {
 | 
			
		||||
  title: string;
 | 
			
		||||
@@ -78,6 +80,10 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
    []
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const promptStore = usePromptStore();
 | 
			
		||||
  const builtinCount = SearchService.count.builtin;
 | 
			
		||||
  const customCount = promptStore.prompts.size ?? 0;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className={styles["window-header"]}>
 | 
			
		||||
@@ -242,6 +248,37 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
            </SettingItem>
 | 
			
		||||
          </div>
 | 
			
		||||
        </List>
 | 
			
		||||
        <List>
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.Prompt.Disable.Title}
 | 
			
		||||
            subTitle={Locale.Settings.Prompt.Disable.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <input
 | 
			
		||||
              type="checkbox"
 | 
			
		||||
              checked={config.disablePromptHint}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.disablePromptHint = e.currentTarget.checked)
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></input>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.Prompt.List}
 | 
			
		||||
            subTitle={Locale.Settings.Prompt.ListCount(
 | 
			
		||||
              builtinCount,
 | 
			
		||||
              customCount
 | 
			
		||||
            )}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<EditIcon />}
 | 
			
		||||
              text={Locale.Settings.Prompt.Edit}
 | 
			
		||||
              onClick={() => showToast(Locale.WIP)}
 | 
			
		||||
            />
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
        </List>
 | 
			
		||||
        <List>
 | 
			
		||||
          {enabledAccessControl ? (
 | 
			
		||||
            <SettingItem
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ export function ListItem(props: { children: JSX.Element[] }) {
 | 
			
		||||
  return <div className={styles["list-item"]}>{props.children}</div>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function List(props: { children: JSX.Element[] }) {
 | 
			
		||||
export function List(props: { children: JSX.Element[] | JSX.Element }) {
 | 
			
		||||
  return <div className={styles.list}>{props.children}</div>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								app/icons/edit.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/icons/edit.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0)  rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path  id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(10.5 11)  rotate(0 1.4166666666666665 1.8333333333333333)" d="M2.83,0L2.83,3C2.83,3.37 2.53,3.67 2.17,3.67L0,3.67 " /><path  id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(2.6666666666666665 1.3333333333333333)  rotate(0 5.333333333333333 6.666666666666666)" d="M10.67,4L10.67,0.67C10.67,0.3 10.37,0 10,0L0.67,0C0.3,0 0,0.3 0,0.67L0,12.67C0,13.03 0.3,13.33 0.67,13.33L2.67,13.33 " /><path  id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.333333333333333 5.333333333333333)  rotate(0 2.333333333333333 0)" d="M0,0L4.67,0 " /><path  id="路径 4" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(7.666666666666666 7.666666666666666)  rotate(0 2.833333333333333 3.5)" d="M0,7L5.67,0 " /><path  id="路径 5" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.333333333333333 8)  rotate(0 1.3333333333333333 0)" d="M0,0L2.67,0 " /></g></g></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.6 KiB  | 
@@ -62,6 +62,16 @@ const cn = {
 | 
			
		||||
    SendKey: "发送键",
 | 
			
		||||
    Theme: "主题",
 | 
			
		||||
    TightBorder: "紧凑边框",
 | 
			
		||||
    Prompt: {
 | 
			
		||||
      Disable: {
 | 
			
		||||
        Title: "禁用提示词自动补全",
 | 
			
		||||
        SubTitle: "禁用后将无法自动根据输入补全",
 | 
			
		||||
      },
 | 
			
		||||
      List: "自定义提示词列表",
 | 
			
		||||
      ListCount: (builtin: number, custom: number) =>
 | 
			
		||||
        `内置 ${builtin} 条,用户定义 ${custom} 条`,
 | 
			
		||||
      Edit: "编辑",
 | 
			
		||||
    },
 | 
			
		||||
    HistoryCount: {
 | 
			
		||||
      Title: "附带历史消息数",
 | 
			
		||||
      SubTitle: "每次请求携带的历史消息数",
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,8 @@ export interface ChatConfig {
 | 
			
		||||
  theme: Theme;
 | 
			
		||||
  tightBorder: boolean;
 | 
			
		||||
 | 
			
		||||
  disablePromptHint: boolean;
 | 
			
		||||
 | 
			
		||||
  modelConfig: {
 | 
			
		||||
    model: string;
 | 
			
		||||
    temperature: number;
 | 
			
		||||
@@ -124,6 +126,8 @@ const DEFAULT_CONFIG: ChatConfig = {
 | 
			
		||||
  theme: Theme.Auto as Theme,
 | 
			
		||||
  tightBorder: false,
 | 
			
		||||
 | 
			
		||||
  disablePromptHint: false,
 | 
			
		||||
 | 
			
		||||
  modelConfig: {
 | 
			
		||||
    model: "gpt-3.5-turbo",
 | 
			
		||||
    temperature: 1,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import JsSearch from "js-search";
 | 
			
		||||
import Fuse from "fuse.js";
 | 
			
		||||
import { showToast } from "../components/ui-lib";
 | 
			
		||||
 | 
			
		||||
export interface Prompt {
 | 
			
		||||
  id?: number;
 | 
			
		||||
  shortcut: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  content: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -22,36 +22,30 @@ export const PROMPT_KEY = "prompt-store";
 | 
			
		||||
 | 
			
		||||
export const SearchService = {
 | 
			
		||||
  ready: false,
 | 
			
		||||
  progress: 0, // 0 - 1, 1 means ready
 | 
			
		||||
  engine: new JsSearch.Search("prompts"),
 | 
			
		||||
  deleted: new Set<number>(),
 | 
			
		||||
  engine: new Fuse<Prompt>([], { keys: ["title"] }),
 | 
			
		||||
  count: {
 | 
			
		||||
    builtin: 0,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async init(prompts: PromptStore["prompts"]) {
 | 
			
		||||
    this.engine.addIndex("id");
 | 
			
		||||
    this.engine.addIndex("shortcut");
 | 
			
		||||
    this.engine.addIndex("title");
 | 
			
		||||
 | 
			
		||||
    const n = prompts.size;
 | 
			
		||||
    let count = 0;
 | 
			
		||||
    for await (const prompt of prompts.values()) {
 | 
			
		||||
      this.engine.addDocument(prompt);
 | 
			
		||||
      count += 1;
 | 
			
		||||
      this.progress = count / n;
 | 
			
		||||
  init(prompts: Prompt[]) {
 | 
			
		||||
    if (this.ready) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.engine.setCollection(prompts);
 | 
			
		||||
    this.ready = true;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  remove(id: number) {
 | 
			
		||||
    this.deleted.add(id);
 | 
			
		||||
    this.engine.remove((doc) => doc.id === id);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  add(prompt: Prompt) {
 | 
			
		||||
    this.engine.addDocument(prompt);
 | 
			
		||||
    this.engine.add(prompt);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  search(text: string) {
 | 
			
		||||
    const results = this.engine.search(text) as Prompt[];
 | 
			
		||||
    return results.filter((v) => !v.id || !this.deleted.has(v.id));
 | 
			
		||||
    const results = this.engine.search(text);
 | 
			
		||||
    return results.map((v) => v.item);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -91,6 +85,35 @@ export const usePromptStore = create<PromptStore>()(
 | 
			
		||||
    {
 | 
			
		||||
      name: PROMPT_KEY,
 | 
			
		||||
      version: 1,
 | 
			
		||||
      onRehydrateStorage(state) {
 | 
			
		||||
        const PROMPT_URL = "./prompts.json";
 | 
			
		||||
 | 
			
		||||
        type PromptList = Array<[string, string]>;
 | 
			
		||||
 | 
			
		||||
        fetch(PROMPT_URL)
 | 
			
		||||
          .then((res) => res.json())
 | 
			
		||||
          .then((res) => {
 | 
			
		||||
            const builtinPrompts = [res.en, res.cn]
 | 
			
		||||
              .map((promptList: PromptList) => {
 | 
			
		||||
                return promptList.map(
 | 
			
		||||
                  ([title, content]) =>
 | 
			
		||||
                    ({
 | 
			
		||||
                      title,
 | 
			
		||||
                      content,
 | 
			
		||||
                    } as Prompt)
 | 
			
		||||
                );
 | 
			
		||||
              })
 | 
			
		||||
              .concat([...(state?.prompts?.values() ?? [])]);
 | 
			
		||||
 | 
			
		||||
            const allPromptsForSearch = builtinPrompts.reduce(
 | 
			
		||||
              (pre, cur) => pre.concat(cur),
 | 
			
		||||
              []
 | 
			
		||||
            );
 | 
			
		||||
            SearchService.count.builtin = res.en.length + res.cn.length;
 | 
			
		||||
            SearchService.init(allPromptsForSearch);
 | 
			
		||||
            showToast(`已加载 ${allPromptsForSearch.length} 条 Prompts`);
 | 
			
		||||
          });
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@svgr/webpack": "^6.5.1",
 | 
			
		||||
    "@types/js-search": "^1.4.0",
 | 
			
		||||
    "@types/node": "^18.14.6",
 | 
			
		||||
    "@types/react": "^18.0.28",
 | 
			
		||||
    "@types/react-dom": "^18.0.11",
 | 
			
		||||
@@ -24,7 +23,7 @@
 | 
			
		||||
    "eslint": "8.35.0",
 | 
			
		||||
    "eslint-config-next": "13.2.3",
 | 
			
		||||
    "eventsource-parser": "^0.1.0",
 | 
			
		||||
    "js-search": "^2.0.0",
 | 
			
		||||
    "fuse.js": "^6.6.2",
 | 
			
		||||
    "next": "^13.2.3",
 | 
			
		||||
    "node-fetch": "^3.3.1",
 | 
			
		||||
    "openai": "^3.2.1",
 | 
			
		||||
@@ -38,6 +37,7 @@
 | 
			
		||||
    "sass": "^1.59.2",
 | 
			
		||||
    "spark-md5": "^3.0.2",
 | 
			
		||||
    "typescript": "4.9.5",
 | 
			
		||||
    "use-debounce": "^9.0.3",
 | 
			
		||||
    "zustand": "^4.3.6"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -1320,11 +1320,6 @@
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/unist" "*"
 | 
			
		||||
 | 
			
		||||
"@types/js-search@^1.4.0":
 | 
			
		||||
  version "1.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/js-search/-/js-search-1.4.0.tgz#f2d4afa176a4fc7b17fb46a1593847887fa1fb7b"
 | 
			
		||||
  integrity sha512-OMDWvQP2AmxpQI9vFh7U/TzExNGB9Sj9WQCoxUR8VXZEv6jM4cyNzLODkh1gkBHJ9Er7kdasChzEpba4FxLGaA==
 | 
			
		||||
 | 
			
		||||
"@types/json5@^0.0.29":
 | 
			
		||||
  version "0.0.29"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
 | 
			
		||||
@@ -2524,6 +2519,11 @@ functions-have-names@^1.2.2:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
 | 
			
		||||
  integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
 | 
			
		||||
 | 
			
		||||
fuse.js@^6.6.2:
 | 
			
		||||
  version "6.6.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
 | 
			
		||||
  integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
 | 
			
		||||
 | 
			
		||||
gensync@^1.0.0-beta.2:
 | 
			
		||||
  version "1.0.0-beta.2"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
 | 
			
		||||
@@ -3042,11 +3042,6 @@ js-sdsl@^4.1.4:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"
 | 
			
		||||
  integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==
 | 
			
		||||
 | 
			
		||||
js-search@^2.0.0:
 | 
			
		||||
  version "2.0.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/js-search/-/js-search-2.0.0.tgz#84dc9d44e34ca0870d067e04b86d8038b77edc26"
 | 
			
		||||
  integrity sha512-lJ8KzjlwcelIWuAdKyzsXv45W6OIwRpayzc7XmU8mzgWadg5UVOKVmnq/tXudddEB9Ceic3tVaGu6QOK/eebhg==
 | 
			
		||||
 | 
			
		||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
 | 
			
		||||
@@ -4660,6 +4655,11 @@ uri-js@^4.2.2:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    punycode "^2.1.0"
 | 
			
		||||
 | 
			
		||||
use-debounce@^9.0.3:
 | 
			
		||||
  version "9.0.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.3.tgz#bac660c19ab7b38662e08608fee23c7ad303f532"
 | 
			
		||||
  integrity sha512-FhtlbDtDXILJV7Lix5OZj5yX/fW1tzq+VrvK1fnT2bUrPOGruU9Rw8NCEn+UI9wopfERBEZAOQ8lfeCJPllgnw==
 | 
			
		||||
 | 
			
		||||
use-sync-external-store@1.2.0:
 | 
			
		||||
  version "1.2.0"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user