mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-09 19:46:37 +08:00
Merge branch 'Yidadaa:main' into main
This commit is contained in:
commit
fe7fc59d0b
12
README.md
12
README.md
@ -11,7 +11,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
|
|
||||||
[Demo](https://chat-gpt-next-web.vercel.app/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
[Demo](https://chat-gpt-next-web.vercel.app/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||||
|
|
||||||
[演示](https://chat-gpt-next-web.vercel.app/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/231789746-41f34d05-6ef9-43f3-a1d1-ff109d4c3c14.jpg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
[演示](https://chat-gpt-next-web.vercel.app/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/233002565-139daa1a-eb3a-4a12-ac37-6418e7a15d36.png) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
||||||
|
|
||||||
@ -35,10 +35,12 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
- [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
|
- [ ] Desktop App with tauri
|
||||||
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
|
- [ ] 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)
|
- [ ] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
|
|
||||||
### Not in Plan
|
### Not in Plan
|
||||||
|
|
||||||
@ -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)
|
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||||
- [ ] 允许用户自行编辑内置 Prompt 列表
|
- [x] 允许用户自行编辑内置 Prompt 列表
|
||||||
|
- [ ] 提示词模板:使用预制上下文快速定制新对话
|
||||||
|
- [ ] 分享为图片,分享到 ShareGPT
|
||||||
- [ ] 使用 tauri 打包桌面应用
|
- [ ] 使用 tauri 打包桌面应用
|
||||||
- [ ] 支持自部署的大语言模型
|
- [ ] 支持自部署的大语言模型
|
||||||
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
|
@ -18,10 +18,15 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
console.log("[Proxy] ", openaiPath);
|
console.log("[Proxy] ", openaiPath);
|
||||||
console.log("[Base Url]", baseUrl);
|
console.log("[Base Url]", baseUrl);
|
||||||
|
|
||||||
|
if (process.env.OPENAI_ORG_ID) {
|
||||||
|
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(`${baseUrl}/${openaiPath}`, {
|
return fetch(`${baseUrl}/${openaiPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
...(process.env.OPENAI_ORG_ID && { "OpenAI-Organization": process.env.OPENAI_ORG_ID }),
|
||||||
},
|
},
|
||||||
method: req.method,
|
method: req.method,
|
||||||
body: req.body,
|
body: req.body,
|
||||||
|
@ -570,10 +570,9 @@ export function Chat(props: {
|
|||||||
if (userIndex === null) return;
|
if (userIndex === null) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore
|
const content = session.messages[userIndex].content;
|
||||||
.onUserInput(session.messages[userIndex].content)
|
|
||||||
.then(() => setIsLoading(false));
|
|
||||||
deleteMessage(userIndex);
|
deleteMessage(userIndex);
|
||||||
|
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -709,6 +708,7 @@ export function Chat(props: {
|
|||||||
className={styles["chat-body"]}
|
className={styles["chat-body"]}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||||
|
onMouseDown={() => inputRef.current?.blur()}
|
||||||
onWheel={(e) => setAutoScroll(hitBottom && e.deltaY > 0)}
|
onWheel={(e) => setAutoScroll(hitBottom && e.deltaY > 0)}
|
||||||
onTouchStart={() => {
|
onTouchStart={() => {
|
||||||
inputRef.current?.blur();
|
inputRef.current?.blur();
|
||||||
|
@ -32,3 +32,63 @@
|
|||||||
min-width: 80%;
|
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";
|
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 ResetIcon from "../icons/reload.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
|
import CopyIcon from "../icons/copy.svg";
|
||||||
import ClearIcon from "../icons/clear.svg";
|
import ClearIcon from "../icons/clear.svg";
|
||||||
import EditIcon from "../icons/edit.svg";
|
import EditIcon from "../icons/edit.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import EyeOffIcon from "../icons/eye-off.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 { IconButton } from "./button";
|
||||||
import {
|
import {
|
||||||
@ -26,14 +27,114 @@ import {
|
|||||||
import { Avatar } from "./chat";
|
import { Avatar } from "./chat";
|
||||||
|
|
||||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
||||||
import { getEmojiUrl } from "../utils";
|
import { copyToClipboard, getEmojiUrl } from "../utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { UPDATE_URL } from "../constant";
|
import { UPDATE_URL } from "../constant";
|
||||||
import { SearchService, usePromptStore } from "../store/prompt";
|
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||||
import { requestUsage } from "../requests";
|
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
import { InputRange } from "./input-range";
|
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: {
|
function SettingItem(props: {
|
||||||
title: string;
|
title: string;
|
||||||
subTitle?: string;
|
subTitle?: string;
|
||||||
@ -99,16 +200,14 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const [usage, setUsage] = useState<{
|
const usage = {
|
||||||
used?: number;
|
used: updateStore.used,
|
||||||
subscription?: number;
|
subscription: updateStore.subscription,
|
||||||
}>();
|
};
|
||||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||||
function checkUsage() {
|
function checkUsage() {
|
||||||
setLoadingUsage(true);
|
setLoadingUsage(true);
|
||||||
requestUsage()
|
updateStore.updateUsage().finally(() => {
|
||||||
.then((res) => setUsage(res))
|
|
||||||
.finally(() => {
|
|
||||||
setLoadingUsage(false);
|
setLoadingUsage(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -122,10 +221,12 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
|
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
const builtinCount = SearchService.count.builtin;
|
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();
|
const showUsage = accessStore.isAuthorized();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// checks per minutes
|
||||||
checkUpdate();
|
checkUpdate();
|
||||||
showUsage && checkUsage();
|
showUsage && checkUsage();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -469,7 +570,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
text={Locale.Settings.Prompt.Edit}
|
text={Locale.Settings.Prompt.Edit}
|
||||||
onClick={() => showToast(Locale.WIP)}
|
onClick={() => setShowPromptModal(true)}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</List>
|
</List>
|
||||||
@ -555,6 +656,10 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
></InputRange>
|
></InputRange>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
{shouldShowPromptModal && (
|
||||||
|
<UserPromptModal onClose={() => setShowPromptModal(false)} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
box-shadow: var(--card-shadow);
|
box-shadow: var(--card-shadow);
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 50vw;
|
width: 60vw;
|
||||||
animation: slide-in ease 0.3s;
|
animation: slide-in ease 0.3s;
|
||||||
|
|
||||||
--modal-padding: 20px;
|
--modal-padding: 20px;
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="38"
|
|
||||||
height="38" viewBox="0 0 38 38" fill="none">
|
|
||||||
<defs>
|
|
||||||
<filter id="filter_0" x="-4" y="-4" width="38" height="38" filterUnits="userSpaceOnUse"
|
|
||||||
color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix"
|
|
||||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
|
||||||
<feOffset dx="0" dy="2" />
|
|
||||||
<feGaussianBlur stdDeviation="2" />
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" />
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_Shadow" />
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_Shadow" result="shape" />
|
|
||||||
</filter>
|
|
||||||
<rect id="path_0" x="0" y="0" width="30" height="30" />
|
|
||||||
</defs>
|
|
||||||
<g opacity="1" transform="translate(4 2) rotate(0 15 15)">
|
|
||||||
<g id="undefined" filter="url(#filter_0)">
|
|
||||||
<rect stroke="#000000" stroke-width="1" stroke-opacity="0.05" />
|
|
||||||
<rect x="0" y="0" width="30" height="30" rx="10" />
|
|
||||||
</g>
|
|
||||||
<mask id="bg-mask-0" fill="white">
|
|
||||||
<use xlink:href="#path_0"></use>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#bg-mask-0)">
|
|
||||||
<g opacity="1" transform="translate(6 4.5) rotate(0 9 11)">
|
|
||||||
<text>
|
|
||||||
<tspan x="0" y="16.240000000000002" font-size="14" line-height="0" fill="#000000"
|
|
||||||
opacity="1" font-family="SourceHanSansCN-Regular" letter-spacing="0">🤣</tspan>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
@ -38,12 +38,12 @@ const cn = {
|
|||||||
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "历史记忆",
|
Title: "历史摘要",
|
||||||
EmptyContent: "尚未记忆",
|
EmptyContent: "尚未总结",
|
||||||
Send: "发送记忆",
|
Send: "启用总结并发送摘要",
|
||||||
Copy: "复制记忆",
|
Copy: "复制摘要",
|
||||||
Reset: "重置对话",
|
Reset: "重置对话",
|
||||||
ResetConfirm: "重置后将清空当前对话记录以及历史记忆,确认重置?",
|
ResetConfirm: "重置后将清空当前对话记录以及历史摘要,确认重置?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "新的聊天",
|
NewChat: "新的聊天",
|
||||||
@ -59,10 +59,10 @@ const cn = {
|
|||||||
ResetAll: "重置所有选项",
|
ResetAll: "重置所有选项",
|
||||||
Close: "关闭",
|
Close: "关闭",
|
||||||
ConfirmResetAll: {
|
ConfirmResetAll: {
|
||||||
Confirm: "Are you sure you want to reset all configurations?",
|
Confirm: "确认清除所有配置?",
|
||||||
},
|
},
|
||||||
ConfirmClearAll: {
|
ConfirmClearAll: {
|
||||||
Confirm: "Are you sure you want to reset all chat?",
|
Confirm: "确认清除所有聊天记录?",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
@ -105,6 +105,11 @@ const cn = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`内置 ${builtin} 条,用户定义 ${custom} 条`,
|
`内置 ${builtin} 条,用户定义 ${custom} 条`,
|
||||||
Edit: "编辑",
|
Edit: "编辑",
|
||||||
|
Modal: {
|
||||||
|
Title: "提示词列表",
|
||||||
|
Add: "增加一条",
|
||||||
|
Search: "搜索提示词",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "附带历史消息数",
|
Title: "附带历史消息数",
|
||||||
|
@ -107,6 +107,11 @@ const de: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} integriert, ${custom} benutzerdefiniert`,
|
`${builtin} integriert, ${custom} benutzerdefiniert`,
|
||||||
Edit: "Bearbeiten",
|
Edit: "Bearbeiten",
|
||||||
|
Modal: {
|
||||||
|
Title: "Prompt List",
|
||||||
|
Add: "Add One",
|
||||||
|
Search: "Search Prompts",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Anzahl der angehängten Nachrichten",
|
Title: "Anzahl der angehängten Nachrichten",
|
||||||
|
@ -107,6 +107,11 @@ const en: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} built-in, ${custom} user-defined`,
|
`${builtin} built-in, ${custom} user-defined`,
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
|
Modal: {
|
||||||
|
Title: "Prompt List",
|
||||||
|
Add: "Add One",
|
||||||
|
Search: "Search Prompts",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Attached Messages Count",
|
Title: "Attached Messages Count",
|
||||||
@ -128,7 +133,7 @@ const en: LocaleType = {
|
|||||||
return `Used this month $${used}, subscription $${total}`;
|
return `Used this month $${used}, subscription $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Checking...",
|
IsChecking: "Checking...",
|
||||||
Check: "Check Again",
|
Check: "Check",
|
||||||
NoAccess: "Enter API Key to check balance",
|
NoAccess: "Enter API Key to check balance",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
|
@ -107,6 +107,11 @@ const es: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} incorporado, ${custom} definido por el usuario`,
|
`${builtin} incorporado, ${custom} definido por el usuario`,
|
||||||
Edit: "Editar",
|
Edit: "Editar",
|
||||||
|
Modal: {
|
||||||
|
Title: "Prompt List",
|
||||||
|
Add: "Add One",
|
||||||
|
Search: "Search Prompts",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Cantidad de mensajes adjuntos",
|
Title: "Cantidad de mensajes adjuntos",
|
||||||
|
@ -107,6 +107,11 @@ const it: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} built-in, ${custom} user-defined`,
|
`${builtin} built-in, ${custom} user-defined`,
|
||||||
Edit: "Modifica",
|
Edit: "Modifica",
|
||||||
|
Modal: {
|
||||||
|
Title: "Prompt List",
|
||||||
|
Add: "Add One",
|
||||||
|
Search: "Search Prompts",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Conteggio dei messaggi allegati",
|
Title: "Conteggio dei messaggi allegati",
|
||||||
|
@ -108,6 +108,11 @@ const jp = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`組み込み ${builtin} 件、ユーザー定義 ${custom} 件`,
|
`組み込み ${builtin} 件、ユーザー定義 ${custom} 件`,
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
|
Modal: {
|
||||||
|
Title: "プロンプトリスト",
|
||||||
|
Add: "新規追加",
|
||||||
|
Search: "プロンプトワード検索",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "履歴メッセージ数を添付",
|
Title: "履歴メッセージ数を添付",
|
||||||
|
@ -107,6 +107,11 @@ const tr: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
||||||
Edit: "Düzenle",
|
Edit: "Düzenle",
|
||||||
|
Modal: {
|
||||||
|
Title: "Prompt List",
|
||||||
|
Add: "Add One",
|
||||||
|
Search: "Search Prompts",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Ekli Mesaj Sayısı",
|
Title: "Ekli Mesaj Sayısı",
|
||||||
|
@ -105,6 +105,11 @@ const tw: LocaleType = {
|
|||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`內置 ${builtin} 條,用戶定義 ${custom} 條`,
|
`內置 ${builtin} 條,用戶定義 ${custom} 條`,
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
|
Modal: {
|
||||||
|
Title: "提示詞列表",
|
||||||
|
Add: "增加一條",
|
||||||
|
Search: "搜尋提示詞",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "附帶歷史訊息數",
|
Title: "附帶歷史訊息數",
|
||||||
@ -155,9 +160,9 @@ const tw: LocaleType = {
|
|||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
||||||
Topic:
|
Topic:
|
||||||
"Summarise the conversation in a short and concise eye-catching title that instantly conveys the main topic. Use as few words as possible. Use the language used in the enquiry, e.g. use English for English enquiry, use zh-hant for traditional chinese enquiry. Don't use quotation marks at the beginning and the end.",
|
"Use the language used by the user (e.g. en for english conversation, zh-hant for chinese conversation, etc.) to generate a title (at most 6 words) summarizing our conversation without any lead-in, quotation marks, preamble like 'Title:', direct text copies, single-word replies, quotation marks, translations, or brackets. Remove enclosing quotation marks. The title should make third-party grasp the essence of the conversation in first sight.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Summarise the conversation in at most 250 tokens for continuing the conversation in future. Use the language used in the conversation, e.g. use English for English conversation, use zh-hant for traditional chinese conversation.",
|
"Use the language used by the user (e.g. en-us for english conversation, zh-hant for chinese conversation, etc.) to summarise the conversation in at most 200 words. The summary will be used as prompt for you to continue the conversation in the future.",
|
||||||
},
|
},
|
||||||
ConfirmClearAll: "確認清除所有對話、設定數據?",
|
ConfirmClearAll: "確認清除所有對話、設定數據?",
|
||||||
},
|
},
|
||||||
|
@ -462,6 +462,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
|
|
||||||
const context = session.context.slice();
|
const context = session.context.slice();
|
||||||
|
|
||||||
|
// long term memory
|
||||||
if (
|
if (
|
||||||
session.sendMemory &&
|
session.sendMemory &&
|
||||||
session.memoryPrompt &&
|
session.memoryPrompt &&
|
||||||
@ -471,9 +472,33 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
context.push(memoryPrompt);
|
context.push(memoryPrompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recentMessages = context.concat(
|
// get short term and unmemoried long term memory
|
||||||
messages.slice(Math.max(0, n - config.historyMessageCount)),
|
const shortTermMemoryMessageIndex = Math.max(
|
||||||
|
0,
|
||||||
|
n - config.historyMessageCount,
|
||||||
);
|
);
|
||||||
|
const longTermMemoryMessageIndex = session.lastSummarizeIndex;
|
||||||
|
const oldestIndex = Math.max(
|
||||||
|
shortTermMemoryMessageIndex,
|
||||||
|
longTermMemoryMessageIndex,
|
||||||
|
);
|
||||||
|
const threshold = config.compressMessageLengthThreshold;
|
||||||
|
|
||||||
|
// get recent messages as many as possible
|
||||||
|
const reversedRecentMessages = [];
|
||||||
|
for (
|
||||||
|
let i = n - 1, count = 0;
|
||||||
|
i >= oldestIndex && count < threshold;
|
||||||
|
i -= 1
|
||||||
|
) {
|
||||||
|
const msg = messages[i];
|
||||||
|
if (!msg || msg.isError) continue;
|
||||||
|
count += msg.content.length;
|
||||||
|
reversedRecentMessages.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// concat
|
||||||
|
const recentMessages = context.concat(reversedRecentMessages.reverse());
|
||||||
|
|
||||||
return recentMessages;
|
return recentMessages;
|
||||||
},
|
},
|
||||||
@ -542,7 +567,10 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
config.compressMessageLengthThreshold,
|
config.compressMessageLengthThreshold,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (historyMsgLength > config.compressMessageLengthThreshold) {
|
if (
|
||||||
|
historyMsgLength > config.compressMessageLengthThreshold &&
|
||||||
|
session.sendMemory
|
||||||
|
) {
|
||||||
requestChatStream(
|
requestChatStream(
|
||||||
toBeSummarizedMsgs.concat({
|
toBeSummarizedMsgs.concat({
|
||||||
role: "system",
|
role: "system",
|
||||||
|
@ -5,62 +5,74 @@ import { getLang } from "../locales";
|
|||||||
|
|
||||||
export interface Prompt {
|
export interface Prompt {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
isUser?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromptStore {
|
export interface PromptStore {
|
||||||
|
counter: number;
|
||||||
latestId: number;
|
latestId: number;
|
||||||
prompts: Map<number, Prompt>;
|
prompts: Record<number, Prompt>;
|
||||||
|
|
||||||
add: (prompt: Prompt) => number;
|
add: (prompt: Prompt) => number;
|
||||||
remove: (id: number) => void;
|
remove: (id: number) => void;
|
||||||
search: (text: string) => Prompt[];
|
search: (text: string) => Prompt[];
|
||||||
|
|
||||||
|
getUserPrompts: () => Prompt[];
|
||||||
|
updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PROMPT_KEY = "prompt-store";
|
export const PROMPT_KEY = "prompt-store";
|
||||||
|
|
||||||
export const SearchService = {
|
export const SearchService = {
|
||||||
ready: false,
|
ready: false,
|
||||||
engine: new Fuse<Prompt>([], { keys: ["title"] }),
|
builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }),
|
||||||
|
userEngine: new Fuse<Prompt>([], { keys: ["title"] }),
|
||||||
count: {
|
count: {
|
||||||
builtin: 0,
|
builtin: 0,
|
||||||
},
|
},
|
||||||
allBuiltInPrompts: [] as Prompt[],
|
allPrompts: [] as Prompt[],
|
||||||
|
builtinPrompts: [] as Prompt[],
|
||||||
|
|
||||||
init(prompts: Prompt[]) {
|
init(builtinPrompts: Prompt[], userPrompts: Prompt[]) {
|
||||||
if (this.ready) {
|
if (this.ready) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.allBuiltInPrompts = prompts;
|
this.allPrompts = userPrompts.concat(builtinPrompts);
|
||||||
this.engine.setCollection(prompts);
|
this.builtinPrompts = builtinPrompts.slice();
|
||||||
|
this.builtinEngine.setCollection(builtinPrompts);
|
||||||
|
this.userEngine.setCollection(userPrompts);
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(id: number) {
|
remove(id: number) {
|
||||||
this.engine.remove((doc) => doc.id === id);
|
this.userEngine.remove((doc) => doc.id === id);
|
||||||
},
|
},
|
||||||
|
|
||||||
add(prompt: Prompt) {
|
add(prompt: Prompt) {
|
||||||
this.engine.add(prompt);
|
this.userEngine.add(prompt);
|
||||||
},
|
},
|
||||||
|
|
||||||
search(text: string) {
|
search(text: string) {
|
||||||
const results = this.engine.search(text);
|
const userResults = this.userEngine.search(text);
|
||||||
return results.map((v) => v.item);
|
const builtinResults = this.builtinEngine.search(text);
|
||||||
|
return userResults.concat(builtinResults).map((v) => v.item);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePromptStore = create<PromptStore>()(
|
export const usePromptStore = create<PromptStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
|
counter: 0,
|
||||||
latestId: 0,
|
latestId: 0,
|
||||||
prompts: new Map(),
|
prompts: {},
|
||||||
|
|
||||||
add(prompt) {
|
add(prompt) {
|
||||||
const prompts = get().prompts;
|
const prompts = get().prompts;
|
||||||
prompt.id = get().latestId + 1;
|
prompt.id = get().latestId + 1;
|
||||||
prompts.set(prompt.id, prompt);
|
prompt.isUser = true;
|
||||||
|
prompts[prompt.id] = prompt;
|
||||||
|
|
||||||
set(() => ({
|
set(() => ({
|
||||||
latestId: prompt.id!,
|
latestId: prompt.id!,
|
||||||
@ -72,19 +84,40 @@ export const usePromptStore = create<PromptStore>()(
|
|||||||
|
|
||||||
remove(id) {
|
remove(id) {
|
||||||
const prompts = get().prompts;
|
const prompts = get().prompts;
|
||||||
prompts.delete(id);
|
delete prompts[id];
|
||||||
SearchService.remove(id);
|
SearchService.remove(id);
|
||||||
|
|
||||||
set(() => ({
|
set(() => ({
|
||||||
prompts,
|
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) {
|
search(text) {
|
||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
// return all prompts
|
// return all rompts
|
||||||
const userPrompts = get().prompts?.values?.() ?? [];
|
return SearchService.allPrompts.concat([...get().getUserPrompts()]);
|
||||||
return SearchService.allBuiltInPrompts.concat([...userPrompts]);
|
|
||||||
}
|
}
|
||||||
return SearchService.search(text) as Prompt[];
|
return SearchService.search(text) as Prompt[];
|
||||||
},
|
},
|
||||||
@ -104,23 +137,27 @@ export const usePromptStore = create<PromptStore>()(
|
|||||||
if (getLang() === "cn") {
|
if (getLang() === "cn") {
|
||||||
fetchPrompts = fetchPrompts.reverse();
|
fetchPrompts = fetchPrompts.reverse();
|
||||||
}
|
}
|
||||||
const builtinPrompts = fetchPrompts
|
const builtinPrompts = fetchPrompts.map(
|
||||||
.map((promptList: PromptList) => {
|
(promptList: PromptList) => {
|
||||||
return promptList.map(
|
return promptList.map(
|
||||||
([title, content]) =>
|
([title, content]) =>
|
||||||
({
|
({
|
||||||
|
id: Math.random(),
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
} as Prompt),
|
} as Prompt),
|
||||||
);
|
);
|
||||||
})
|
},
|
||||||
.concat([...(state?.prompts?.values() ?? [])]);
|
);
|
||||||
|
|
||||||
|
const userPrompts =
|
||||||
|
usePromptStore.getState().getUserPrompts() ?? [];
|
||||||
|
|
||||||
const allPromptsForSearch = builtinPrompts
|
const allPromptsForSearch = builtinPrompts
|
||||||
.reduce((pre, cur) => pre.concat(cur), [])
|
.reduce((pre, cur) => pre.concat(cur), [])
|
||||||
.filter((v) => !!v.title && !!v.content);
|
.filter((v) => !!v.title && !!v.content);
|
||||||
SearchService.count.builtin = res.en.length + res.cn.length;
|
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 { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||||
|
import { requestUsage } from "../requests";
|
||||||
|
|
||||||
export interface UpdateStore {
|
export interface UpdateStore {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
remoteVersion: string;
|
remoteVersion: string;
|
||||||
|
|
||||||
|
used?: number;
|
||||||
|
subscription?: number;
|
||||||
|
lastUpdateUsage: number;
|
||||||
|
|
||||||
version: string;
|
version: string;
|
||||||
getLatestVersion: (force: boolean) => Promise<string>;
|
getLatestVersion: (force?: boolean) => Promise<void>;
|
||||||
|
updateUsage: (force?: boolean) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UPDATE_KEY = "chat-update";
|
export const UPDATE_KEY = "chat-update";
|
||||||
@ -26,22 +32,27 @@ function queryMeta(key: string, defaultValue?: string): string {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
|
||||||
export const useUpdateStore = create<UpdateStore>()(
|
export const useUpdateStore = create<UpdateStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
lastUpdate: 0,
|
lastUpdate: 0,
|
||||||
remoteVersion: "",
|
remoteVersion: "",
|
||||||
|
|
||||||
|
lastUpdateUsage: 0,
|
||||||
|
|
||||||
version: "unknown",
|
version: "unknown",
|
||||||
|
|
||||||
async getLatestVersion(force = false) {
|
async getLatestVersion(force = false) {
|
||||||
set(() => ({ version: queryMeta("version") }));
|
set(() => ({ version: queryMeta("version") ?? "unknown" }));
|
||||||
|
|
||||||
const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
const overTenMins = Date.now() - get().lastUpdate > 10 * ONE_MINUTE;
|
||||||
const shouldFetch = force || overTenMins;
|
if (!force && !overTenMins) return;
|
||||||
if (!shouldFetch) {
|
|
||||||
return get().version ?? "unknown";
|
set(() => ({
|
||||||
}
|
lastUpdate: Date.now(),
|
||||||
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const data = await (await fetch(FETCH_TAG_URL)).json();
|
// 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 data = await (await fetch(FETCH_COMMIT_URL)).json();
|
||||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||||
set(() => ({
|
set(() => ({
|
||||||
lastUpdate: Date.now(),
|
|
||||||
remoteVersion: remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
console.log("[Got Upstream] ", remoteId);
|
console.log("[Got Upstream] ", remoteId);
|
||||||
return remoteId;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", 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 {
|
input {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
@ -224,6 +225,7 @@ input[type="password"] {
|
|||||||
color: var(--black);
|
color: var(--black);
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.math {
|
div.math {
|
||||||
|
Loading…
Reference in New Issue
Block a user