forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
commit
b178bd6bd6
11
README.md
11
README.md
@ -35,7 +35,9 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
||||
## Roadmap
|
||||
|
||||
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||
- [ ] User Prompt: user can edit and save custom prompts to prompt list
|
||||
- [x] User Prompt: user can edit and save custom prompts to prompt list
|
||||
- [ ] Prompt Template: create a new chat with pre-defined in-context prompts
|
||||
- [ ] Share as image, share to ShareGPT
|
||||
- [ ] Desktop App with tauri
|
||||
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
|
||||
- [ ] Plugins: support network search, caculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||
@ -59,7 +61,9 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
||||
## 开发计划
|
||||
|
||||
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||
- [ ] 允许用户自行编辑内置 Prompt 列表
|
||||
- [x] 允许用户自行编辑内置 Prompt 列表
|
||||
- [ ] 提示词模板:使用预制上下文快速定制新对话
|
||||
- [ ] 分享为图片,分享到 ShareGPT
|
||||
- [ ] 使用 tauri 打包桌面应用
|
||||
- [ ] 支持自部署的大语言模型
|
||||
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||
@ -221,6 +225,9 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
||||
[@chazzhou](https://github.com/chazzhou)
|
||||
[@hauy](https://github.com/hauy)
|
||||
[@Corwin006](https://github.com/Corwin006)
|
||||
[@yankunsong](https://github.com/yankunsong)
|
||||
[@ypwhs](https://github.com/ypwhs)
|
||||
[@fxxxchao](https://github.com/fxxxchao)
|
||||
|
||||
### Contributor
|
||||
|
||||
|
@ -17,7 +17,7 @@ async function makeRequest(req: NextRequest) {
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -29,3 +29,7 @@ export async function POST(req: NextRequest) {
|
||||
export async function GET(req: NextRequest) {
|
||||
return makeRequest(req);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: "edge",
|
||||
};
|
||||
|
@ -570,10 +570,9 @@ export function Chat(props: {
|
||||
if (userIndex === null) return;
|
||||
|
||||
setIsLoading(true);
|
||||
chatStore
|
||||
.onUserInput(session.messages[userIndex].content)
|
||||
.then(() => setIsLoading(false));
|
||||
const content = session.messages[userIndex].content;
|
||||
deleteMessage(userIndex);
|
||||
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
|
@ -32,3 +32,63 @@
|
||||
min-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-modal {
|
||||
min-height: 40vh;
|
||||
|
||||
.user-prompt-search {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 10px;
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.user-prompt-list {
|
||||
padding: 10px 0;
|
||||
|
||||
.user-prompt-item {
|
||||
margin-bottom: 10px;
|
||||
widows: 100%;
|
||||
|
||||
.user-prompt-header {
|
||||
display: flex;
|
||||
widows: 100%;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.user-prompt-title {
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
margin-right: 5px;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-prompt-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-prompt-button {
|
||||
height: 100%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-content {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-actions {
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo, HTMLProps } from "react";
|
||||
import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
|
||||
|
||||
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
|
||||
|
||||
@ -6,12 +6,13 @@ import styles from "./settings.module.scss";
|
||||
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import CopyIcon from "../icons/copy.svg";
|
||||
import ClearIcon from "../icons/clear.svg";
|
||||
import EditIcon from "../icons/edit.svg";
|
||||
import EyeIcon from "../icons/eye.svg";
|
||||
import EyeOffIcon from "../icons/eye-off.svg";
|
||||
|
||||
import { List, ListItem, Popover, showToast } from "./ui-lib";
|
||||
import { Input, List, ListItem, Modal, Popover } from "./ui-lib";
|
||||
|
||||
import { IconButton } from "./button";
|
||||
import {
|
||||
@ -26,14 +27,114 @@ import {
|
||||
import { Avatar } from "./chat";
|
||||
|
||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
||||
import { getEmojiUrl } from "../utils";
|
||||
import { copyToClipboard, getEmojiUrl } from "../utils";
|
||||
import Link from "next/link";
|
||||
import { UPDATE_URL } from "../constant";
|
||||
import { SearchService, usePromptStore } from "../store/prompt";
|
||||
import { requestUsage } from "../requests";
|
||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||
import { ErrorBoundary } from "./error";
|
||||
import { InputRange } from "./input-range";
|
||||
|
||||
function UserPromptModal(props: { onClose?: () => void }) {
|
||||
const promptStore = usePromptStore();
|
||||
const userPrompts = promptStore.getUserPrompts();
|
||||
const builtinPrompts = SearchService.builtinPrompts;
|
||||
const allPrompts = userPrompts.concat(builtinPrompts);
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
|
||||
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
|
||||
|
||||
useEffect(() => {
|
||||
if (searchInput.length > 0) {
|
||||
const searchResult = SearchService.search(searchInput);
|
||||
setSearchPrompts(searchResult);
|
||||
} else {
|
||||
setSearchPrompts([]);
|
||||
}
|
||||
}, [searchInput]);
|
||||
|
||||
return (
|
||||
<div className="modal-mask">
|
||||
<Modal
|
||||
title={Locale.Settings.Prompt.Modal.Title}
|
||||
onClose={() => props.onClose?.()}
|
||||
actions={[
|
||||
<IconButton
|
||||
key="add"
|
||||
onClick={() => promptStore.add({ title: "", content: "" })}
|
||||
icon={<ClearIcon />}
|
||||
bordered
|
||||
text={Locale.Settings.Prompt.Modal.Add}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<div className={styles["user-prompt-modal"]}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles["user-prompt-search"]}
|
||||
placeholder={Locale.Settings.Prompt.Modal.Search}
|
||||
value={searchInput}
|
||||
onInput={(e) => setSearchInput(e.currentTarget.value)}
|
||||
></input>
|
||||
|
||||
<div className={styles["user-prompt-list"]}>
|
||||
{prompts.map((v, _) => (
|
||||
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
|
||||
<div className={styles["user-prompt-header"]}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles["user-prompt-title"]}
|
||||
value={v.title}
|
||||
readOnly={!v.isUser}
|
||||
onChange={(e) => {
|
||||
if (v.isUser) {
|
||||
promptStore.updateUserPrompts(
|
||||
v.id!,
|
||||
(prompt) => (prompt.title = e.currentTarget.value),
|
||||
);
|
||||
}
|
||||
}}
|
||||
></input>
|
||||
|
||||
<div className={styles["user-prompt-buttons"]}>
|
||||
{v.isUser && (
|
||||
<IconButton
|
||||
icon={<ClearIcon />}
|
||||
bordered
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => promptStore.remove(v.id!)}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon={<CopyIcon />}
|
||||
bordered
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => copyToClipboard(v.content)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
rows={2}
|
||||
value={v.content}
|
||||
className={styles["user-prompt-content"]}
|
||||
readOnly={!v.isUser}
|
||||
onChange={(e) => {
|
||||
if (v.isUser) {
|
||||
promptStore.updateUserPrompts(
|
||||
v.id!,
|
||||
(prompt) => (prompt.content = e.currentTarget.value),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingItem(props: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
@ -99,18 +200,16 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
});
|
||||
}
|
||||
|
||||
const [usage, setUsage] = useState<{
|
||||
used?: number;
|
||||
subscription?: number;
|
||||
}>();
|
||||
const usage = {
|
||||
used: updateStore.used,
|
||||
subscription: updateStore.subscription,
|
||||
};
|
||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||
function checkUsage() {
|
||||
setLoadingUsage(true);
|
||||
requestUsage()
|
||||
.then((res) => setUsage(res))
|
||||
.finally(() => {
|
||||
setLoadingUsage(false);
|
||||
});
|
||||
updateStore.updateUsage().finally(() => {
|
||||
setLoadingUsage(false);
|
||||
});
|
||||
}
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
@ -122,10 +221,12 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
|
||||
const promptStore = usePromptStore();
|
||||
const builtinCount = SearchService.count.builtin;
|
||||
const customCount = promptStore.prompts.size ?? 0;
|
||||
const customCount = promptStore.getUserPrompts().length ?? 0;
|
||||
const [shouldShowPromptModal, setShowPromptModal] = useState(false);
|
||||
|
||||
const showUsage = accessStore.isAuthorized();
|
||||
useEffect(() => {
|
||||
// checks per minutes
|
||||
checkUpdate();
|
||||
showUsage && checkUsage();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -469,7 +570,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
text={Locale.Settings.Prompt.Edit}
|
||||
onClick={() => showToast(Locale.WIP)}
|
||||
onClick={() => setShowPromptModal(true)}
|
||||
/>
|
||||
</SettingItem>
|
||||
</List>
|
||||
@ -555,6 +656,10 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
></InputRange>
|
||||
</SettingItem>
|
||||
</List>
|
||||
|
||||
{shouldShowPromptModal && (
|
||||
<UserPromptModal onClose={() => setShowPromptModal(false)} />
|
||||
)}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
@ -53,7 +53,7 @@
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--white);
|
||||
border-radius: 12px;
|
||||
width: 50vw;
|
||||
width: 60vw;
|
||||
animation: slide-in ease 0.3s;
|
||||
|
||||
--modal-padding: 20px;
|
||||
|
@ -59,10 +59,10 @@ const cn = {
|
||||
ResetAll: "重置所有选项",
|
||||
Close: "关闭",
|
||||
ConfirmResetAll: {
|
||||
Confirm: "Are you sure you want to reset all configurations?",
|
||||
Confirm: "确认清除所有配置?",
|
||||
},
|
||||
ConfirmClearAll: {
|
||||
Confirm: "Are you sure you want to reset all chat?",
|
||||
Confirm: "确认清除所有聊天记录?",
|
||||
},
|
||||
},
|
||||
Lang: {
|
||||
@ -105,6 +105,11 @@ const cn = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`内置 ${builtin} 条,用户定义 ${custom} 条`,
|
||||
Edit: "编辑",
|
||||
Modal: {
|
||||
Title: "提示词列表",
|
||||
Add: "增加一条",
|
||||
Search: "搜尋提示詞",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "附带历史消息数",
|
||||
|
@ -107,6 +107,11 @@ const de: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} integriert, ${custom} benutzerdefiniert`,
|
||||
Edit: "Bearbeiten",
|
||||
Modal: {
|
||||
Title: "Prompt List",
|
||||
Add: "Add One",
|
||||
Search: "Search Prompts",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "Anzahl der angehängten Nachrichten",
|
||||
|
@ -107,6 +107,11 @@ const en: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} built-in, ${custom} user-defined`,
|
||||
Edit: "Edit",
|
||||
Modal: {
|
||||
Title: "Prompt List",
|
||||
Add: "Add One",
|
||||
Search: "Search Prompts",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "Attached Messages Count",
|
||||
@ -128,7 +133,7 @@ const en: LocaleType = {
|
||||
return `Used this month $${used}, subscription $${total}`;
|
||||
},
|
||||
IsChecking: "Checking...",
|
||||
Check: "Check Again",
|
||||
Check: "Check",
|
||||
NoAccess: "Enter API Key to check balance",
|
||||
},
|
||||
AccessCode: {
|
||||
|
@ -107,6 +107,11 @@ const es: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} incorporado, ${custom} definido por el usuario`,
|
||||
Edit: "Editar",
|
||||
Modal: {
|
||||
Title: "Prompt List",
|
||||
Add: "Add One",
|
||||
Search: "Search Prompts",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "Cantidad de mensajes adjuntos",
|
||||
|
@ -107,6 +107,11 @@ const it: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} built-in, ${custom} user-defined`,
|
||||
Edit: "Modifica",
|
||||
Modal: {
|
||||
Title: "Prompt List",
|
||||
Add: "Add One",
|
||||
Search: "Search Prompts",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "Conteggio dei messaggi allegati",
|
||||
|
@ -108,6 +108,11 @@ const jp = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`組み込み ${builtin} 件、ユーザー定義 ${custom} 件`,
|
||||
Edit: "編集",
|
||||
Modal: {
|
||||
Title: "提示词列表",
|
||||
Add: "增加一条",
|
||||
Search: "搜尋提示詞",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "履歴メッセージ数を添付",
|
||||
|
@ -107,6 +107,11 @@ const tr: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
||||
Edit: "Düzenle",
|
||||
Modal: {
|
||||
Title: "Prompt List",
|
||||
Add: "Add One",
|
||||
Search: "Search Prompts",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "Ekli Mesaj Sayısı",
|
||||
|
@ -105,6 +105,11 @@ const tw: LocaleType = {
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`內置 ${builtin} 條,用戶定義 ${custom} 條`,
|
||||
Edit: "編輯",
|
||||
Modal: {
|
||||
Title: "提示詞列表",
|
||||
Add: "增加一條",
|
||||
Search: "搜索提示词",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "附帶歷史訊息數",
|
||||
|
@ -114,6 +114,10 @@ export async function requestUsage() {
|
||||
response.total_usage = Math.round(response.total_usage) / 100;
|
||||
}
|
||||
|
||||
if (total.hard_limit_usd) {
|
||||
total.hard_limit_usd = Math.round(total.hard_limit_usd * 100) / 100;
|
||||
}
|
||||
|
||||
return {
|
||||
used: response.total_usage,
|
||||
subscription: total.hard_limit_usd,
|
||||
|
@ -5,62 +5,74 @@ import { getLang } from "../locales";
|
||||
|
||||
export interface Prompt {
|
||||
id?: number;
|
||||
isUser?: boolean;
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface PromptStore {
|
||||
counter: number;
|
||||
latestId: number;
|
||||
prompts: Map<number, Prompt>;
|
||||
prompts: Record<number, Prompt>;
|
||||
|
||||
add: (prompt: Prompt) => number;
|
||||
remove: (id: number) => void;
|
||||
search: (text: string) => Prompt[];
|
||||
|
||||
getUserPrompts: () => Prompt[];
|
||||
updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void;
|
||||
}
|
||||
|
||||
export const PROMPT_KEY = "prompt-store";
|
||||
|
||||
export const SearchService = {
|
||||
ready: false,
|
||||
engine: new Fuse<Prompt>([], { keys: ["title"] }),
|
||||
builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }),
|
||||
userEngine: new Fuse<Prompt>([], { keys: ["title"] }),
|
||||
count: {
|
||||
builtin: 0,
|
||||
},
|
||||
allBuiltInPrompts: [] as Prompt[],
|
||||
allPrompts: [] as Prompt[],
|
||||
builtinPrompts: [] as Prompt[],
|
||||
|
||||
init(prompts: Prompt[]) {
|
||||
init(builtinPrompts: Prompt[], userPrompts: Prompt[]) {
|
||||
if (this.ready) {
|
||||
return;
|
||||
}
|
||||
this.allBuiltInPrompts = prompts;
|
||||
this.engine.setCollection(prompts);
|
||||
this.allPrompts = userPrompts.concat(builtinPrompts);
|
||||
this.builtinPrompts = builtinPrompts.slice();
|
||||
this.builtinEngine.setCollection(builtinPrompts);
|
||||
this.userEngine.setCollection(userPrompts);
|
||||
this.ready = true;
|
||||
},
|
||||
|
||||
remove(id: number) {
|
||||
this.engine.remove((doc) => doc.id === id);
|
||||
this.userEngine.remove((doc) => doc.id === id);
|
||||
},
|
||||
|
||||
add(prompt: Prompt) {
|
||||
this.engine.add(prompt);
|
||||
this.userEngine.add(prompt);
|
||||
},
|
||||
|
||||
search(text: string) {
|
||||
const results = this.engine.search(text);
|
||||
return results.map((v) => v.item);
|
||||
const userResults = this.userEngine.search(text);
|
||||
const builtinResults = this.builtinEngine.search(text);
|
||||
return userResults.concat(builtinResults).map((v) => v.item);
|
||||
},
|
||||
};
|
||||
|
||||
export const usePromptStore = create<PromptStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
counter: 0,
|
||||
latestId: 0,
|
||||
prompts: new Map(),
|
||||
prompts: {},
|
||||
|
||||
add(prompt) {
|
||||
const prompts = get().prompts;
|
||||
prompt.id = get().latestId + 1;
|
||||
prompts.set(prompt.id, prompt);
|
||||
prompt.isUser = true;
|
||||
prompts[prompt.id] = prompt;
|
||||
|
||||
set(() => ({
|
||||
latestId: prompt.id!,
|
||||
@ -72,19 +84,40 @@ export const usePromptStore = create<PromptStore>()(
|
||||
|
||||
remove(id) {
|
||||
const prompts = get().prompts;
|
||||
prompts.delete(id);
|
||||
delete prompts[id];
|
||||
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.id - a.id : 0));
|
||||
return userPrompts;
|
||||
},
|
||||
|
||||
updateUserPrompts(id: number, updater) {
|
||||
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) {
|
||||
if (text.length === 0) {
|
||||
// return all prompts
|
||||
const userPrompts = get().prompts?.values?.() ?? [];
|
||||
return SearchService.allBuiltInPrompts.concat([...userPrompts]);
|
||||
// return all rompts
|
||||
return SearchService.allPrompts.concat([...get().getUserPrompts()]);
|
||||
}
|
||||
return SearchService.search(text) as Prompt[];
|
||||
},
|
||||
@ -104,23 +137,27 @@ export const usePromptStore = create<PromptStore>()(
|
||||
if (getLang() === "cn") {
|
||||
fetchPrompts = fetchPrompts.reverse();
|
||||
}
|
||||
const builtinPrompts = fetchPrompts
|
||||
.map((promptList: PromptList) => {
|
||||
const builtinPrompts = fetchPrompts.map(
|
||||
(promptList: PromptList) => {
|
||||
return promptList.map(
|
||||
([title, content]) =>
|
||||
({
|
||||
id: Math.random(),
|
||||
title,
|
||||
content,
|
||||
} as Prompt),
|
||||
);
|
||||
})
|
||||
.concat([...(state?.prompts?.values() ?? [])]);
|
||||
},
|
||||
);
|
||||
|
||||
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;
|
||||
SearchService.init(allPromptsForSearch);
|
||||
SearchService.init(allPromptsForSearch, userPrompts);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -1,13 +1,19 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||
import { requestUsage } from "../requests";
|
||||
|
||||
export interface UpdateStore {
|
||||
lastUpdate: number;
|
||||
remoteVersion: string;
|
||||
|
||||
used?: number;
|
||||
subscription?: number;
|
||||
lastUpdateUsage: number;
|
||||
|
||||
version: string;
|
||||
getLatestVersion: (force: boolean) => Promise<string>;
|
||||
getLatestVersion: (force?: boolean) => Promise<void>;
|
||||
updateUsage: (force?: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export const UPDATE_KEY = "chat-update";
|
||||
@ -26,22 +32,27 @@ function queryMeta(key: string, defaultValue?: string): string {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
export const useUpdateStore = create<UpdateStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
lastUpdate: 0,
|
||||
remoteVersion: "",
|
||||
|
||||
lastUpdateUsage: 0,
|
||||
|
||||
version: "unknown",
|
||||
|
||||
async getLatestVersion(force = false) {
|
||||
set(() => ({ version: queryMeta("version") }));
|
||||
set(() => ({ version: queryMeta("version") ?? "unknown" }));
|
||||
|
||||
const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
||||
const shouldFetch = force || overTenMins;
|
||||
if (!shouldFetch) {
|
||||
return get().version ?? "unknown";
|
||||
}
|
||||
const overTenMins = Date.now() - get().lastUpdate > 10 * ONE_MINUTE;
|
||||
if (!force && !overTenMins) return;
|
||||
|
||||
set(() => ({
|
||||
lastUpdate: Date.now(),
|
||||
}));
|
||||
|
||||
try {
|
||||
// const data = await (await fetch(FETCH_TAG_URL)).json();
|
||||
@ -49,14 +60,26 @@ export const useUpdateStore = create<UpdateStore>()(
|
||||
const data = await (await fetch(FETCH_COMMIT_URL)).json();
|
||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||
set(() => ({
|
||||
lastUpdate: Date.now(),
|
||||
remoteVersion: remoteId,
|
||||
}));
|
||||
console.log("[Got Upstream] ", remoteId);
|
||||
return remoteId;
|
||||
} catch (error) {
|
||||
console.error("[Fetch Upstream Commit Id]", error);
|
||||
return get().version ?? "";
|
||||
}
|
||||
},
|
||||
|
||||
async updateUsage(force = false) {
|
||||
const overOneMinute = Date.now() - get().lastUpdateUsage >= ONE_MINUTE;
|
||||
if (!overOneMinute && !force) return;
|
||||
|
||||
set(() => ({
|
||||
lastUpdateUsage: Date.now(),
|
||||
}));
|
||||
|
||||
const usage = await requestUsage();
|
||||
|
||||
if (usage) {
|
||||
set(() => usage);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
@ -140,6 +140,7 @@ label {
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
@ -224,6 +225,7 @@ input[type="password"] {
|
||||
color: var(--black);
|
||||
padding: 0 10px;
|
||||
max-width: 50%;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
div.math {
|
||||
|
@ -19,7 +19,7 @@
|
||||
"emoji-picker-react": "^4.4.7",
|
||||
"eventsource-parser": "^0.1.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"next": "^13.3.0",
|
||||
"next": "^13.3.1-canary.8",
|
||||
"node-fetch": "^3.3.1",
|
||||
"openai": "^3.2.1",
|
||||
"react": "^18.2.0",
|
||||
|
108
yarn.lock
108
yarn.lock
@ -1099,10 +1099,10 @@
|
||||
"@jridgewell/resolve-uri" "3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||
|
||||
"@next/env@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/env/-/env-13.3.0.tgz#cc2e49f03060a4684ce7ec7fd617a21bc5b9edba"
|
||||
integrity sha512-AjppRV4uG3No7L1plinoTQETH+j2F10TEnrMfzbTUYwze5sBUPveeeBAPZPm8OkJZ1epq9OyYKhZrvbD6/9HCQ==
|
||||
"@next/env@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.1-canary.8.tgz#9f5cf57999e4f4b59ef6407924803a247cc4e451"
|
||||
integrity sha512-xZfNu7yq3OfiC4rkGuGMcqb25se+ZHRqajSdny8dp+nZzkNSK1SHuNT3W8faI+KGk6dqzO/zAdHR9YrqnQlCAg==
|
||||
|
||||
"@next/eslint-plugin-next@13.2.3":
|
||||
version "13.2.3"
|
||||
@ -1111,50 +1111,50 @@
|
||||
dependencies:
|
||||
glob "7.1.7"
|
||||
|
||||
"@next/swc-darwin-arm64@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.0.tgz#38f18e0639cd4c7edc6a38d4b83fe00f38eea4f2"
|
||||
integrity sha512-DmIQCNq6JtccLPPBzf0dgh2vzMWt5wjxbP71pCi5EWpWYE3MsP6FcRXi4MlAmFNDQOfcFXR2r7kBeG1LpZUh1w==
|
||||
"@next/swc-darwin-arm64@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.1-canary.8.tgz#66786ba76d37c210c184739624c6f84eaf2dc52b"
|
||||
integrity sha512-BLbvhcaSzwuXbREOmJiqAdXVD7Jl9830hDY5ZTTNg7hXqEZgoMg2LxAEmtaaBMVZRfDQjd5bH3QPBV8fbG4UKg==
|
||||
|
||||
"@next/swc-darwin-x64@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.0.tgz#b670ed1fd1d231aa21279173ec52e3ad56dc6aeb"
|
||||
integrity sha512-oQoqFa88OGgwnYlnAGHVct618FRI/749se0N3S8t9Bzdv5CRbscnO0RcX901+YnNK4Q6yeiizfgO3b7kogtsZg==
|
||||
"@next/swc-darwin-x64@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.1-canary.8.tgz#289296bd3cc55db7fef42037eb89ce4a6260ba31"
|
||||
integrity sha512-n4tJKPIvFTZshS1TVWrsqaW7h9VW+BmguO/AlZ3Q3NJ9hWxC5L4lxn2T6CTQ4M30Gf+t5u+dPzYLQ5IDtJFnFQ==
|
||||
|
||||
"@next/swc-linux-arm64-gnu@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.0.tgz#b114935f6b4c94c123f6cac55a4823d483209ba5"
|
||||
integrity sha512-Wzz2p/WqAJUqTVoLo6H18WMeAXo3i+9DkPDae4oQG8LMloJ3if4NEZTnOnTUlro6cq+S/W4pTGa97nWTrOjbGw==
|
||||
"@next/swc-linux-arm64-gnu@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.1-canary.8.tgz#dc79e8005849b6482241b460abdce9334665c766"
|
||||
integrity sha512-AxnsgZ56whwVAeejyEZMk8xc8Vapwzb3Zn0YdZzPCR42WKfkcSkM+AWfq33zUOZnjvCmQBDyfHIo4CURVweR6g==
|
||||
|
||||
"@next/swc-linux-arm64-musl@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.0.tgz#67a57309f8761c7d00d629d6785d56ed0567a0d2"
|
||||
integrity sha512-xPVrIQOQo9WXJYgmoTlMnAD/HlR/1e1ZIWGbwIzEirXBVBqMARUulBEIKdC19zuvoJ477qZJgBDCKtKEykCpyQ==
|
||||
"@next/swc-linux-arm64-musl@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.1-canary.8.tgz#f70873add4aad7ced36f760d1640adc008b7dc03"
|
||||
integrity sha512-zc7rzhtrHMWZ/phvjCNplHGo+ZLembjtluI5J8Xl4iwQQCyZwAtnmQhs37/zkdi6dHZou+wcFBZWRz14awRDBw==
|
||||
|
||||
"@next/swc-linux-x64-gnu@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.0.tgz#11bd2bea7c00b40be111c0dd16e71171f3792086"
|
||||
integrity sha512-jOFlpGuPD7W2tuXVJP4wt9a3cpNxWAPcloq5EfMJRiXsBBOjLVFZA7boXYxEBzSVgUiVVr1V9T0HFM7pULJ1qA==
|
||||
"@next/swc-linux-x64-gnu@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.1-canary.8.tgz#fe81b8033628c6cf74e154f2db8c8c7f1593008f"
|
||||
integrity sha512-vNbFDiuZ9fWmcznlilDbflZLb04evWPUQlyDT7Tqjd964PlSIaaX3tr64pdYjJOljDaqTr2Kbx0YW74mWF/PEw==
|
||||
|
||||
"@next/swc-linux-x64-musl@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.0.tgz#d57e99f85890799b78719c3ea32a4624de8d701b"
|
||||
integrity sha512-2OwKlzaBgmuet9XYHc3KwsEilzb04F540rlRXkAcjMHL7eCxB7uZIGtsVvKOnQLvC/elrUegwSw1+5f7WmfyOw==
|
||||
"@next/swc-linux-x64-musl@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.1-canary.8.tgz#ada4585046a7937f96f2d39fc4aaca12826dde5f"
|
||||
integrity sha512-/FVBPJEBDZYCNraocRWtd5ObAgNi9VFnzJYGYDYIj4jKkFRWWm/CaWu9A7toQACC/JDy262uPyDPathXT9BAqQ==
|
||||
|
||||
"@next/swc-win32-arm64-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.0.tgz#0c209aa35d1c88b01e78259a89cd68f4139b5093"
|
||||
integrity sha512-OeHiA6YEvndxT46g+rzFK/MQTfftKxJmzslERMu9LDdC6Kez0bdrgEYed5eXFK2Z1viKZJCGRlhd06rBusyztA==
|
||||
"@next/swc-win32-arm64-msvc@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.1-canary.8.tgz#21b4f6c4be61845759753df9313bd9bcbb241969"
|
||||
integrity sha512-8jMwRCeI26yVZLPwG0AjOi4b1yqSeqYmbHA7r+dqiV0OgFdYjnbyHU1FmiKDaC5SnnJN6LWV2Qjer9GDD0Kcuw==
|
||||
|
||||
"@next/swc-win32-ia32-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.0.tgz#52ae74da1dd6d840c3743923367d27ed013803dd"
|
||||
integrity sha512-4aB7K9mcVK1lYEzpOpqWrXHEZympU3oK65fnNcY1Qc4HLJFLJj8AViuqQd4jjjPNuV4sl8jAwTz3gN5VNGWB7w==
|
||||
"@next/swc-win32-ia32-msvc@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.1-canary.8.tgz#e23192e1d1b1a32b0eb805363b02360c5b523a77"
|
||||
integrity sha512-kcYB9iSEikFhv0I9uQDdgQ2lm8i3O8LA+GhnED9e5VtURBwOSwED7c6ZpaRQBYSPgnEA9/xiJVChICE/I7Ig1g==
|
||||
|
||||
"@next/swc-win32-x64-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.0.tgz#db7b55fee834dc8c2c484c696469e65bae2ee770"
|
||||
integrity sha512-Reer6rkLLcoOvB0dd66+Y7WrWVFH7sEEkF/4bJCIfsSKnTStTYaHtwIJAwbqnt9I392Tqvku0KkoqZOryWV9LQ==
|
||||
"@next/swc-win32-x64-msvc@13.3.1-canary.8":
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.1-canary.8.tgz#a3f29404955cba2193de5e74fd5d9fcfdcb0ab51"
|
||||
integrity sha512-UKrGHonKVWBNg+HI4J8pXE6Jjjl8GwjhygFau71s8M0+jSy99y5Y+nGH9EmMNWKNvrObukyYvrs6OsAusKdCqw==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
@ -3924,27 +3924,27 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next@^13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1"
|
||||
integrity sha512-OVTw8MpIPa12+DCUkPqRGPS3thlJPcwae2ZL4xti3iBff27goH024xy4q2lhlsdoYiKOi8Kz6uJoLW/GXwgfOA==
|
||||
next@^13.3.1-canary.8:
|
||||
version "13.3.1-canary.8"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-13.3.1-canary.8.tgz#f0846e5eada1491884326786a0749d5adc04c24d"
|
||||
integrity sha512-z4QUgyAN+hSWSEqb4pvGvC3iRktE6NH2DVLU4AvfqNYpzP+prePiJC8HN/cJpFhGW9YbhyRLi5FliDC631OOag==
|
||||
dependencies:
|
||||
"@next/env" "13.3.0"
|
||||
"@next/env" "13.3.1-canary.8"
|
||||
"@swc/helpers" "0.4.14"
|
||||
busboy "1.6.0"
|
||||
caniuse-lite "^1.0.30001406"
|
||||
postcss "8.4.14"
|
||||
styled-jsx "5.1.1"
|
||||
optionalDependencies:
|
||||
"@next/swc-darwin-arm64" "13.3.0"
|
||||
"@next/swc-darwin-x64" "13.3.0"
|
||||
"@next/swc-linux-arm64-gnu" "13.3.0"
|
||||
"@next/swc-linux-arm64-musl" "13.3.0"
|
||||
"@next/swc-linux-x64-gnu" "13.3.0"
|
||||
"@next/swc-linux-x64-musl" "13.3.0"
|
||||
"@next/swc-win32-arm64-msvc" "13.3.0"
|
||||
"@next/swc-win32-ia32-msvc" "13.3.0"
|
||||
"@next/swc-win32-x64-msvc" "13.3.0"
|
||||
"@next/swc-darwin-arm64" "13.3.1-canary.8"
|
||||
"@next/swc-darwin-x64" "13.3.1-canary.8"
|
||||
"@next/swc-linux-arm64-gnu" "13.3.1-canary.8"
|
||||
"@next/swc-linux-arm64-musl" "13.3.1-canary.8"
|
||||
"@next/swc-linux-x64-gnu" "13.3.1-canary.8"
|
||||
"@next/swc-linux-x64-musl" "13.3.1-canary.8"
|
||||
"@next/swc-win32-arm64-msvc" "13.3.1-canary.8"
|
||||
"@next/swc-win32-ia32-msvc" "13.3.1-canary.8"
|
||||
"@next/swc-win32-x64-msvc" "13.3.1-canary.8"
|
||||
|
||||
node-domexception@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user