Compare commits

...

110 Commits

Author SHA1 Message Date
GH Action - Upstream Sync
97368f54dd Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-09-05 00:41:50 +00:00
Lloyd Zhou
85704570f3 Merge pull request #5356 from ConnectAI-E/feature/indexdb
fix: hydrated for indexedDB
2024-09-04 19:14:18 +08:00
lloydzhou
53dcae9e9c update 2024-09-04 13:00:18 +08:00
Dogtiti
ed9aae531e fix: hydrated 2024-09-03 20:29:01 +08:00
Dogtiti
886ffc0af8 fix: hydrated for indexedDB 2024-09-03 17:12:48 +08:00
GH Action - Upstream Sync
f51a6a6b6e Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-30 00:41:30 +00:00
Dogtiti
97a4a910e0 Merge pull request #5335 from ConnectAI-E/fix/right-click
fix: right click
2024-08-29 20:49:31 +08:00
Dogtiti
19c7a84548 fix: right click 2024-08-29 20:48:04 +08:00
GH Action - Upstream Sync
488bf51b93 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-28 00:40:48 +00:00
Lloyd Zhou
b6bb1673d4 Merge pull request #5324 from ConnectAI-E/feature/indexdb
feat: add indexDB
2024-08-27 18:07:20 +08:00
Dogtiti
7b6fe66f2a feat: try catch indexedDB error 2024-08-27 10:05:37 +08:00
Dogtiti
c2fc0b4979 feat: try catch indexedDB error 2024-08-27 09:57:07 +08:00
Dogtiti
0b758941a4 feat: clear indexDB 2024-08-26 21:23:21 +08:00
Dogtiti
492b55c893 feat: add indexDB 2024-08-26 21:20:07 +08:00
Dogtiti
4060e367ad feat: add indexDB 2024-08-26 21:13:35 +08:00
GH Action - Upstream Sync
91b69c4fb7 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-25 00:43:53 +00:00
Lloyd Zhou
718782f5b1 Merge pull request #5309 from ElricLiu/main
Update README.md
2024-08-24 12:11:46 +08:00
ElricLiu
0c3fb5b2ce Update README.md
add monica sponsored
2024-08-23 17:28:00 +08:00
GH Action - Upstream Sync
3be687a28a Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-22 00:40:45 +00:00
mayfwl
4ec6b067e7 fix: artifact render error (#5306)
fix: artifact render error
2024-08-21 18:48:44 +08:00
Dogtiti
1748dd6a3b Merge pull request #5303 from ConnectAI-E/Modifylang
Modify View All Languages
2024-08-21 16:28:36 +08:00
mayfwl
95332e50ed Merge branch 'main' into Modifylang 2024-08-21 15:45:04 +08:00
lyf
8496980cf9 Modify View All Languages 2024-08-21 14:28:39 +08:00
Lloyd Zhou
ffe32694b0 Merge pull request #5300 from ConnectAI-E/hotfix/hide-button
Hotfix/hide button
2024-08-21 14:12:03 +08:00
lloydzhou
3d5b21154b update 2024-08-21 12:05:03 +08:00
lloydzhou
4b9697e336 fix: typescript error 2024-08-21 11:54:48 +08:00
lloydzhou
b0e9a542ba frat: add reload button 2024-08-21 11:17:00 +08:00
lloydzhou
8b67536c23 fix: 修复多余的查看全部 2024-08-21 10:28:34 +08:00
lloydzhou
cd49c12181 fix: 修复查看全部按钮导致artifacts失效 2024-08-21 10:27:37 +08:00
GH Action - Upstream Sync
b2e364e6dd Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-21 00:39:36 +00:00
Lloyd Zhou
a6b14c7910 Merge pull request #5274 from Movelocity/feat/search-history
feat: add a page to search chat history
2024-08-21 00:58:01 +08:00
heweikang
e275abdb9c match the origin format 2024-08-20 19:49:47 +08:00
heweikang
09a90665d5 Merge branch 'feat/search-history' of https://github.com/Movelocity/ChatGPT-Next-Web into feat/search-history 2024-08-20 19:47:41 +08:00
heweikang
6649fbdfd0 remove an empty line 2024-08-20 19:47:36 +08:00
heweikang
64a0ffee7b Merge branch 'main' into feat/search-history 2024-08-20 19:47:03 +08:00
Hollway
b529118f31 Merge branch 'ChatGPTNextWeb:main' into feat/search-history 2024-08-20 19:46:08 +08:00
heweikang
39d7d9f13a migrate the search button to plugins discovery 2024-08-20 19:44:22 +08:00
heweikang
fcd55df969 wrap doSearch with useCallback 2024-08-20 09:45:34 +08:00
GH Action - Upstream Sync
c5ab4d80d1 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-20 00:39:42 +00:00
Dogtiti
1e59948358 Merge pull request #5288 from zhangjian10/main
fix: Determine if Tencent is authorized
2024-08-19 09:20:58 +08:00
zhangjian10
1102ef6e6b fix: Determine if Tencent is authorized 2024-08-18 23:47:23 +08:00
heweikang
7ce2e8f4c4 resolve a warning 2024-08-17 11:26:38 +08:00
heweikang
fd1c656bdd add all translations for SearchChat 2024-08-17 11:08:38 +08:00
heweikang
82298a760a Merge branch 'main' into feat/search-history 2024-08-17 10:15:49 +08:00
heweikang
b84bb72e07 add null check for search content 2024-08-17 10:06:56 +08:00
GH Action - Upstream Sync
b9702a7902 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-17 00:38:36 +00:00
E.da
09b56c9399 fix: CUSTOM_MODELS optional configuration -all,+xxx causes the custom model to be unusable
`CUSTOM_MODELS ` optional configuration ` -all,+xxx ` causes the custom model to be unusable
2024-08-16 10:50:09 +08:00
heweikang
e3f499be0c hide search button text 2024-08-16 10:23:27 +08:00
GH Action - Upstream Sync
4aae37b90c Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-16 00:39:26 +00:00
Hollway
86220573b6 Update yarn.lock to match origin version 2024-08-15 17:27:14 +08:00
heweikang
65ed6b02a4 Merge branch 'main' into feat/search-history 2024-08-15 17:24:17 +08:00
heweikang
98093a1f31 make search result item easier to click 2024-08-15 17:21:39 +08:00
heweikang
00990dc195 use yarn instead of npm 2024-08-15 17:09:41 +08:00
heweikang
3da5284a07 优化搜索算法,更新图标 2024-08-15 12:38:20 +08:00
heweikang
cd920364f8 Add page to search chat history 2024-08-14 22:28:05 +08:00
GH Action - Upstream Sync
2565f5a398 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-14 00:39:55 +00:00
GH Action - Upstream Sync
2c0a73049d Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-09 00:40:02 +00:00
GH Action - Upstream Sync
57a2ce222c Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-08 00:39:37 +00:00
GH Action - Upstream Sync
76eb67d7ac Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-07 00:39:54 +00:00
GH Action - Upstream Sync
b07af51649 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-06 00:39:22 +00:00
GH Action - Upstream Sync
735b8cd8b0 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-04 00:42:56 +00:00
GH Action - Upstream Sync
240058cd32 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-03 00:38:27 +00:00
GH Action - Upstream Sync
433c2f37bd Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-02 00:39:07 +00:00
GH Action - Upstream Sync
548e39eb82 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-31 00:32:12 +00:00
GH Action - Upstream Sync
84789c5f26 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-30 00:39:17 +00:00
GH Action - Upstream Sync
24ae8b2015 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-27 00:38:37 +00:00
GH Action - Upstream Sync
d6223e45a4 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-26 00:38:46 +00:00
GH Action - Upstream Sync
0a985fe891 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-23 00:39:22 +00:00
GH Action - Upstream Sync
33f63f3463 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-21 00:42:35 +00:00
GH Action - Upstream Sync
770c265567 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-20 00:38:03 +00:00
GH Action - Upstream Sync
89f1daa226 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-19 02:28:09 +00:00
GH Action - Upstream Sync
914c825725 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-18 00:38:19 +00:00
GH Action - Upstream Sync
d8b774ccfe Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-17 00:39:17 +00:00
GH Action - Upstream Sync
af5de8bdb6 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-16 00:39:21 +00:00
GH Action - Upstream Sync
397a7f56d4 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-14 00:42:50 +00:00
GH Action - Upstream Sync
15d9f82081 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-13 00:38:50 +00:00
GH Action - Upstream Sync
ec653812b9 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-12 00:38:18 +00:00
GH Action - Upstream Sync
8159ebe671 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-11 00:39:05 +00:00
GH Action - Upstream Sync
ccd22742f7 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-10 00:38:55 +00:00
GH Action - Upstream Sync
6b078392ee Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-07 00:42:05 +00:00
GH Action - Upstream Sync
741bd325d6 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-06 00:36:48 +00:00
GH Action - Upstream Sync
01b28f76e5 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-05 00:37:51 +00:00
GH Action - Upstream Sync
1e125b2fdd Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-04 00:38:06 +00:00
GH Action - Upstream Sync
9848ef22ec Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-07-02 00:38:00 +00:00
GH Action - Upstream Sync
7466c75534 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-06-27 00:37:44 +00:00
GH Action - Upstream Sync
3373db8fc7 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-06-25 00:37:18 +00:00
GH Action - Upstream Sync
18d7ec8a01 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-06-08 00:37:12 +00:00
GH Action - Upstream Sync
c6ed7720dc Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-28 00:36:26 +00:00
GH Action - Upstream Sync
724b35d7b5 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-23 00:35:53 +00:00
GH Action - Upstream Sync
1dd1a80f91 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-21 00:36:19 +00:00
GH Action - Upstream Sync
963cdceb83 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-17 00:36:04 +00:00
GH Action - Upstream Sync
90bb307486 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-16 00:35:30 +00:00
GH Action - Upstream Sync
c67b79785e Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-15 00:35:46 +00:00
GH Action - Upstream Sync
c3f91a89b9 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-14 00:35:42 +00:00
GH Action - Upstream Sync
9e4518d5ca Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-08 00:29:15 +00:00
GH Action - Upstream Sync
d3e801780e Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-07 00:35:08 +00:00
GH Action - Upstream Sync
783c314a46 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-05-01 00:37:47 +00:00
GH Action - Upstream Sync
eda303bb0f Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-26 00:34:42 +00:00
GH Action - Upstream Sync
ee1658e826 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-17 00:34:27 +00:00
GH Action - Upstream Sync
3f21cb5435 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-16 00:33:58 +00:00
GH Action - Upstream Sync
9a911fc146 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-15 02:54:48 +00:00
GH Action - Upstream Sync
fcf3de17a5 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-13 00:29:02 +00:00
GH Action - Upstream Sync
2729326131 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-12 00:34:32 +00:00
GH Action - Upstream Sync
f3dcf6ece0 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-11 00:34:42 +00:00
GH Action - Upstream Sync
3acd5fb986 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-10 00:33:53 +00:00
GH Action - Upstream Sync
c8a97db52e Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-09 00:34:26 +00:00
GH Action - Upstream Sync
8e8560417a Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-04-04 00:35:05 +00:00
GH Action - Upstream Sync
101c2602ae Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-03-29 00:33:41 +00:00
GH Action - Upstream Sync
1d8839ae74 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-03-28 00:33:48 +00:00
GH Action - Upstream Sync
3eb6235bf1 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-03-27 00:33:21 +00:00
35 changed files with 667 additions and 84 deletions

View File

@@ -30,6 +30,8 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
[<img src="https://github.com/user-attachments/assets/903482d4-3e87-4134-9af1-f2588fa90659" height="60" width="288" >](https://monica.im/?utm=nxcrp)
</div>
## Enterprise Edition

View File

@@ -1,4 +1,11 @@
import { useEffect, useState, useRef, useMemo } from "react";
import {
useEffect,
useState,
useRef,
useMemo,
forwardRef,
useImperativeHandle,
} from "react";
import { useParams } from "react-router";
import { useWindowSize } from "@/app/utils";
import { IconButton } from "./button";
@@ -8,6 +15,7 @@ import CopyIcon from "../icons/copy.svg";
import DownloadIcon from "../icons/download.svg";
import GithubIcon from "../icons/github.svg";
import LoadingButtonIcon from "../icons/loading.svg";
import ReloadButtonIcon from "../icons/reload.svg";
import Locale from "../locales";
import { Modal, showToast } from "./ui-lib";
import { copyToClipboard, downloadAs } from "../utils";
@@ -15,73 +23,89 @@ import { Path, ApiPath, REPO_URL } from "@/app/constant";
import { Loading } from "./home";
import styles from "./artifacts.module.scss";
export function HTMLPreview(props: {
type HTMLPreviewProps = {
code: string;
autoHeight?: boolean;
height?: number | string;
onLoad?: (title?: string) => void;
}) {
const ref = useRef<HTMLIFrameElement>(null);
const frameId = useRef<string>(nanoid());
const [iframeHeight, setIframeHeight] = useState(600);
const [title, setTitle] = useState("");
/*
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
* 1. using srcdoc
* 2. using src with dataurl:
* easy to share
* length limit (Data URIs cannot be larger than 32,768 characters.)
*/
};
useEffect(() => {
const handleMessage = (e: any) => {
const { id, height, title } = e.data;
setTitle(title);
if (id == frameId.current) {
setIframeHeight(height);
export type HTMLPreviewHander = {
reload: () => void;
};
export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
function HTMLPreview(props, ref) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [frameId, setFrameId] = useState<string>(nanoid());
const [iframeHeight, setIframeHeight] = useState(600);
const [title, setTitle] = useState("");
/*
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
* 1. using srcdoc
* 2. using src with dataurl:
* easy to share
* length limit (Data URIs cannot be larger than 32,768 characters.)
*/
useEffect(() => {
const handleMessage = (e: any) => {
const { id, height, title } = e.data;
setTitle(title);
if (id == frameId) {
setIframeHeight(height);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [frameId]);
useImperativeHandle(ref, () => ({
reload: () => {
setFrameId(nanoid());
},
}));
const height = useMemo(() => {
if (!props.autoHeight) return props.height || 600;
if (typeof props.height === "string") {
return props.height;
}
const parentHeight = props.height || 600;
return iframeHeight + 40 > parentHeight
? parentHeight
: iframeHeight + 40;
}, [props.autoHeight, props.height, iframeHeight]);
const srcDoc = useMemo(() => {
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
if (props.code.includes("<!DOCTYPE html>")) {
props.code.replace("<!DOCTYPE html>", "<!DOCTYPE html>" + script);
}
return script + props.code;
}, [props.code, frameId]);
const handleOnLoad = () => {
if (props?.onLoad) {
props.onLoad(title);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, []);
const height = useMemo(() => {
if (!props.autoHeight) return props.height || 600;
if (typeof props.height === "string") {
return props.height;
}
const parentHeight = props.height || 600;
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
}, [props.autoHeight, props.height, iframeHeight]);
const srcDoc = useMemo(() => {
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
if (props.code.includes("</head>")) {
props.code.replace("</head>", "</head>" + script);
}
return props.code + script;
}, [props.code]);
const handleOnLoad = () => {
if (props?.onLoad) {
props.onLoad(title);
}
};
return (
<iframe
className={styles["artifacts-iframe"]}
id={frameId.current}
ref={ref}
sandbox="allow-forms allow-modals allow-scripts"
style={{ height }}
srcDoc={srcDoc}
onLoad={handleOnLoad}
/>
);
}
return (
<iframe
className={styles["artifacts-iframe"]}
key={frameId}
ref={iframeRef}
sandbox="allow-forms allow-modals allow-scripts"
style={{ height }}
srcDoc={srcDoc}
onLoad={handleOnLoad}
/>
);
},
);
export function ArtifactsShareButton({
getCode,
@@ -184,6 +208,7 @@ export function Artifacts() {
const [code, setCode] = useState("");
const [loading, setLoading] = useState(true);
const [fileName, setFileName] = useState("");
const previewRef = useRef<HTMLPreviewHander>(null);
useEffect(() => {
if (id) {
@@ -208,6 +233,13 @@ export function Artifacts() {
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
<IconButton bordered icon={<GithubIcon />} shadow />
</a>
<IconButton
bordered
style={{ marginLeft: 20 }}
icon={<ReloadButtonIcon />}
shadow
onClick={() => previewRef.current?.reload()}
/>
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
<ArtifactsShareButton
id={id}
@@ -220,6 +252,7 @@ export function Artifacts() {
{code && (
<HTMLPreview
code={code}
ref={previewRef}
autoHeight={false}
height={"100%"}
onLoad={(title) => {

View File

@@ -1587,7 +1587,7 @@ function _Chat() {
message.content.length === 0 &&
!isUser
}
onContextMenu={(e) => onRightClick(e, message)}
// onContextMenu={(e) => onRightClick(e, message)} // hard to use
onDoubleClickCapture={() => {
if (!isMobileScreen) return;
setUserInput(getMessageTextContent(message));

View File

@@ -59,6 +59,13 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
loading: () => <Loading noLogo />,
});
const SearchChat = dynamic(
async () => (await import("./search-chat")).SearchChatPage,
{
loading: () => <Loading noLogo />,
},
);
const Sd = dynamic(async () => (await import("./sd")).Sd, {
loading: () => <Loading noLogo />,
});
@@ -174,6 +181,7 @@ function Screen() {
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.SearchChat} element={<SearchChat />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>

View File

@@ -8,14 +8,21 @@ import RehypeHighlight from "rehype-highlight";
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
import { copyToClipboard, useWindowSize } from "../utils";
import mermaid from "mermaid";
import Locale from "../locales";
import LoadingIcon from "../icons/three-dots.svg";
import ReloadButtonIcon from "../icons/reload.svg";
import React from "react";
import { useDebouncedCallback } from "use-debounce";
import { showImageModal, FullScreen } from "./ui-lib";
import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
import {
ArtifactsShareButton,
HTMLPreview,
HTMLPreviewHander,
} from "./artifacts";
import { Plugin } from "../constant";
import { useChatStore } from "../store";
import { IconButton } from "./button";
export function Mermaid(props: { code: string }) {
const ref = useRef<HTMLDivElement>(null);
const [hasError, setHasError] = useState(false);
@@ -64,7 +71,7 @@ export function Mermaid(props: { code: string }) {
export function PreCode(props: { children: any }) {
const ref = useRef<HTMLPreElement>(null);
const refText = ref.current?.innerText;
const previewRef = useRef<HTMLPreviewHander>(null);
const [mermaidCode, setMermaidCode] = useState("");
const [htmlCode, setHtmlCode] = useState("");
const { height } = useWindowSize();
@@ -79,6 +86,7 @@ export function PreCode(props: { children: any }) {
setMermaidCode((mermaidDom as HTMLElement).innerText);
}
const htmlDom = ref.current.querySelector("code.language-html");
const refText = ref.current.querySelector("code")?.innerText;
if (htmlDom) {
setHtmlCode((htmlDom as HTMLElement).innerText);
} else if (refText?.startsWith("<!DOCTYPE")) {
@@ -86,11 +94,6 @@ export function PreCode(props: { children: any }) {
}
}, 600);
useEffect(() => {
setTimeout(renderArtifacts, 1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refText]);
const enableArtifacts = useMemo(
() => plugins?.includes(Plugin.Artifacts),
[plugins],
@@ -119,6 +122,7 @@ export function PreCode(props: { children: any }) {
codeElement.style.whiteSpace = "pre-wrap";
}
});
setTimeout(renderArtifacts, 1);
}
}, []);
@@ -145,7 +149,15 @@ export function PreCode(props: { children: any }) {
style={{ position: "absolute", right: 20, top: 10 }}
getCode={() => htmlCode}
/>
<IconButton
style={{ position: "absolute", right: 120, top: 10 }}
bordered
icon={<ReloadButtonIcon />}
shadow
onClick={() => previewRef.current?.reload()}
/>
<HTMLPreview
ref={previewRef}
code={htmlCode}
autoHeight={!document.fullscreenElement}
height={!document.fullscreenElement ? 600 : height}
@@ -182,16 +194,14 @@ function CustomCode(props: { children: any }) {
}}
>
{props.children}
{showToggle && collapsed && (
<div
className={`show-hide-button ${
collapsed ? "collapsed" : "expanded"
}`}
>
<button onClick={toggleCollapsed}></button>
</div>
)}
</code>
{showToggle && collapsed && (
<div
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
>
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
</div>
)}
</>
);
}

View File

@@ -0,0 +1,167 @@
import { useState, useEffect, useRef, useCallback } from "react";
import { ErrorBoundary } from "./error";
import styles from "./mask.module.scss";
import { useNavigate } from "react-router-dom";
import { IconButton } from "./button";
import CloseIcon from "../icons/close.svg";
import EyeIcon from "../icons/eye.svg";
import Locale from "../locales";
import { Path } from "../constant";
import { useChatStore } from "../store";
type Item = {
id: number;
name: string;
content: string;
};
export function SearchChatPage() {
const navigate = useNavigate();
const chatStore = useChatStore();
const sessions = chatStore.sessions;
const selectSession = chatStore.selectSession;
const [searchResults, setSearchResults] = useState<Item[]>([]);
const previousValueRef = useRef<string>("");
const searchInputRef = useRef<HTMLInputElement>(null);
const doSearch = useCallback((text: string) => {
const lowerCaseText = text.toLowerCase();
const results: Item[] = [];
sessions.forEach((session, index) => {
const fullTextContents: string[] = [];
session.messages.forEach((message) => {
const content = message.content as string;
if (!content.toLowerCase || content === "") return;
const lowerCaseContent = content.toLowerCase();
// full text search
let pos = lowerCaseContent.indexOf(lowerCaseText);
while (pos !== -1) {
const start = Math.max(0, pos - 35);
const end = Math.min(content.length, pos + lowerCaseText.length + 35);
fullTextContents.push(content.substring(start, end));
pos = lowerCaseContent.indexOf(
lowerCaseText,
pos + lowerCaseText.length,
);
}
});
if (fullTextContents.length > 0) {
results.push({
id: index,
name: session.topic,
content: fullTextContents.join("... "), // concat content with...
});
}
});
// sort by length of matching content
results.sort((a, b) => b.content.length - a.content.length);
return results;
}, []);
useEffect(() => {
const intervalId = setInterval(() => {
if (searchInputRef.current) {
const currentValue = searchInputRef.current.value;
if (currentValue !== previousValueRef.current) {
if (currentValue.length > 0) {
const result = doSearch(currentValue);
setSearchResults(result);
}
previousValueRef.current = currentValue;
}
}
}, 1000);
// Cleanup the interval on component unmount
return () => clearInterval(intervalId);
}, [doSearch]);
return (
<ErrorBoundary>
<div className={styles["mask-page"]}>
{/* header */}
<div className="window-header">
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.SearchChat.Page.Title}
</div>
<div className="window-header-submai-title">
{Locale.SearchChat.Page.SubTitle(searchResults.length)}
</div>
</div>
<div className="window-actions">
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
bordered
onClick={() => navigate(-1)}
/>
</div>
</div>
</div>
<div className={styles["mask-page-body"]}>
<div className={styles["mask-filter"]}>
{/**搜索输入框 */}
<input
type="text"
className={styles["search-bar"]}
placeholder={Locale.SearchChat.Page.Search}
autoFocus
ref={searchInputRef}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const searchText = e.currentTarget.value;
if (searchText.length > 0) {
const result = doSearch(searchText);
setSearchResults(result);
}
}
}}
/>
</div>
<div>
{searchResults.map((item) => (
<div
className={styles["mask-item"]}
key={item.id}
onClick={() => {
navigate(Path.Chat);
selectSession(item.id);
}}
style={{ cursor: "pointer" }}
>
{/** 搜索匹配的文本 */}
<div className={styles["mask-header"]}>
<div className={styles["mask-title"]}>
<div className={styles["mask-name"]}>{item.name}</div>
{item.content.slice(0, 70)}
</div>
</div>
{/** 操作按钮 */}
<div className={styles["mask-actions"]}>
<IconButton
icon={<EyeIcon />}
text={Locale.SearchChat.Item.View}
/>
</div>
</div>
))}
</div>
</div>
</div>
</ErrorBoundary>
);
}

View File

@@ -1,3 +1,5 @@
import path from "path";
export const OWNER = "ChatGPTNextWeb";
export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
@@ -41,6 +43,7 @@ export enum Path {
Sd = "/sd",
SdNew = "/sd-new",
Artifacts = "/artifacts",
SearchChat = "/search-chat",
}
export enum ApiPath {
@@ -475,4 +478,7 @@ export const internalAllowedWebDavEndpoints = [
];
export const DEFAULT_GA_ID = "G-89WN60ZK2E";
export const PLUGINS = [{ name: "Stable Diffusion", path: Path.Sd }];
export const PLUGINS = [
{ name: "Stable Diffusion", path: Path.Sd },
{ name: "Search Chat", path: Path.SearchChat },
];

1
app/icons/zoom.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1.2rem" height="1.2rem" viewBox="0 0 24 24"><g fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></g></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -459,6 +459,21 @@ const ar: PartialLocaleType = {
FineTuned: {
Sysmessage: "أنت مساعد",
},
SearchChat: {
Name: "بحث",
Page: {
Title: "البحث في سجلات الدردشة",
Search: "أدخل كلمات البحث",
NoResult: "لم يتم العثور على نتائج",
NoData: "لا توجد بيانات",
Loading: "جارٍ التحميل",
SubTitle: (count: number) => `تم العثور على ${count} نتائج`,
},
Item: {
View: "عرض",
},
},
Mask: {
Name: "القناع",
Page: {

View File

@@ -466,6 +466,21 @@ const bn: PartialLocaleType = {
FineTuned: {
Sysmessage: "আপনি একজন সহকারী",
},
SearchChat: {
Name: "অনুসন্ধান",
Page: {
Title: "চ্যাট রেকর্ড অনুসন্ধান করুন",
Search: "অনুসন্ধান কীওয়ার্ড লিখুন",
NoResult: "কোন ফলাফল পাওয়া যায়নি",
NoData: "কোন তথ্য নেই",
Loading: "লোড হচ্ছে",
SubTitle: (count: number) => `${count} টি ফলাফল পাওয়া গেছে`,
},
Item: {
View: "দেখুন",
},
},
Mask: {
Name: "মাস্ক",
Page: {

View File

@@ -519,6 +519,21 @@ const cn = {
FineTuned: {
Sysmessage: "你是一个助手",
},
SearchChat: {
Name: "搜索",
Page: {
Title: "搜索聊天记录",
Search: "输入搜索关键词",
NoResult: "没有找到结果",
NoData: "没有数据",
Loading: "加载中",
SubTitle: (count: number) => `搜索到 ${count} 条结果`,
},
Item: {
View: "查看",
},
},
Mask: {
Name: "面具",
Page: {

View File

@@ -467,6 +467,21 @@ const cs: PartialLocaleType = {
FineTuned: {
Sysmessage: "Jste asistent",
},
SearchChat: {
Name: "Hledat",
Page: {
Title: "Hledat v historii chatu",
Search: "Zadejte hledané klíčové slovo",
NoResult: "Nebyly nalezeny žádné výsledky",
NoData: "Žádná data",
Loading: "Načítání",
SubTitle: (count: number) => `Nalezeno ${count} výsledků`,
},
Item: {
View: "Zobrazit",
},
},
Mask: {
Name: "Maska",
Page: {

View File

@@ -482,6 +482,21 @@ const de: PartialLocaleType = {
FineTuned: {
Sysmessage: "Du bist ein Assistent",
},
SearchChat: {
Name: "Suche",
Page: {
Title: "Chatverlauf durchsuchen",
Search: "Suchbegriff eingeben",
NoResult: "Keine Ergebnisse gefunden",
NoData: "Keine Daten",
Loading: "Laden",
SubTitle: (count: number) => `${count} Ergebnisse gefunden`,
},
Item: {
View: "Ansehen",
},
},
Mask: {
Name: "Masken",
Page: {

View File

@@ -527,6 +527,21 @@ const en: LocaleType = {
FineTuned: {
Sysmessage: "You are an assistant that",
},
SearchChat: {
Name: "Search",
Page: {
Title: "Search Chat History",
Search: "Enter search query to search chat history",
NoResult: "No results found",
NoData: "No data",
Loading: "Loading...",
SubTitle: (count: number) => `Found ${count} results`,
},
Item: {
View: "View",
},
},
Mask: {
Name: "Mask",
Page: {

View File

@@ -480,6 +480,21 @@ const es: PartialLocaleType = {
FineTuned: {
Sysmessage: "Eres un asistente",
},
SearchChat: {
Name: "Buscar",
Page: {
Title: "Buscar en el historial de chat",
Search: "Ingrese la palabra clave de búsqueda",
NoResult: "No se encontraron resultados",
NoData: "Sin datos",
Loading: "Cargando",
SubTitle: (count: number) => `Se encontraron ${count} resultados`,
},
Item: {
View: "Ver",
},
},
Mask: {
Name: "Máscara",
Page: {

View File

@@ -480,6 +480,21 @@ const fr: PartialLocaleType = {
FineTuned: {
Sysmessage: "Vous êtes un assistant",
},
SearchChat: {
Name: "Recherche",
Page: {
Title: "Rechercher dans l'historique des discussions",
Search: "Entrez le mot-clé de recherche",
NoResult: "Aucun résultat trouvé",
NoData: "Aucune donnée",
Loading: "Chargement",
SubTitle: (count: number) => `${count} résultats trouvés`,
},
Item: {
View: "Voir",
},
},
Mask: {
Name: "Masque",
Page: {

View File

@@ -470,6 +470,21 @@ const id: PartialLocaleType = {
FineTuned: {
Sysmessage: "Anda adalah seorang asisten",
},
SearchChat: {
Name: "Cari",
Page: {
Title: "Cari riwayat obrolan",
Search: "Masukkan kata kunci pencarian",
NoResult: "Tidak ada hasil ditemukan",
NoData: "Tidak ada data",
Loading: "Memuat",
SubTitle: (count: number) => `Ditemukan ${count} hasil`,
},
Item: {
View: "Lihat",
},
},
Mask: {
Name: "Masker",
Page: {

View File

@@ -481,6 +481,21 @@ const it: PartialLocaleType = {
FineTuned: {
Sysmessage: "Sei un assistente",
},
SearchChat: {
Name: "Cerca",
Page: {
Title: "Cerca nei messaggi",
Search: "Inserisci parole chiave per la ricerca",
NoResult: "Nessun risultato trovato",
NoData: "Nessun dato",
Loading: "Caricamento in corso",
SubTitle: (count: number) => `Trovati ${count} risultati`,
},
Item: {
View: "Visualizza",
},
},
Mask: {
Name: "Maschera",
Page: {

View File

@@ -460,9 +460,27 @@ const jp: PartialLocaleType = {
Plugin: {
Name: "プラグイン",
},
Discovery: {
Name: "発見",
},
FineTuned: {
Sysmessage: "あなたはアシスタントです",
},
SearchChat: {
Name: "検索",
Page: {
Title: "チャット履歴を検索",
Search: "検索キーワードを入力",
NoResult: "結果が見つかりませんでした",
NoData: "データがありません",
Loading: "読み込み中",
SubTitle: (count: number) => `${count} 件の結果が見つかりました`,
},
Item: {
View: "表示",
},
},
Mask: {
Name: "マスク",
Page: {

View File

@@ -458,6 +458,21 @@ const ko: PartialLocaleType = {
FineTuned: {
Sysmessage: "당신은 보조자입니다.",
},
SearchChat: {
Name: "검색",
Page: {
Title: "채팅 기록 검색",
Search: "검색어 입력",
NoResult: "결과를 찾을 수 없습니다",
NoData: "데이터가 없습니다",
Loading: "로딩 중",
SubTitle: (count: number) => `${count}개의 결과를 찾았습니다`,
},
Item: {
View: "보기",
},
},
Mask: {
Name: "마스크",
Page: {

View File

@@ -474,6 +474,21 @@ const no: PartialLocaleType = {
FineTuned: {
Sysmessage: "Du er en assistent",
},
SearchChat: {
Name: "Søk",
Page: {
Title: "Søk i chatthistorikk",
Search: "Skriv inn søkeord",
NoResult: "Ingen resultater funnet",
NoData: "Ingen data",
Loading: "Laster inn",
SubTitle: (count: number) => `Fant ${count} resultater`,
},
Item: {
View: "Vis",
},
},
Mask: {
Name: "Maske",
Page: {

View File

@@ -405,6 +405,21 @@ const pt: PartialLocaleType = {
FineTuned: {
Sysmessage: "Você é um assistente que",
},
SearchChat: {
Name: "Pesquisar",
Page: {
Title: "Pesquisar histórico de chat",
Search: "Digite palavras-chave para pesquisa",
NoResult: "Nenhum resultado encontrado",
NoData: "Sem dados",
Loading: "Carregando",
SubTitle: (count: number) => `Encontrado ${count} resultados`,
},
Item: {
View: "Ver",
},
},
Mask: {
Name: "Máscara",
Page: {

View File

@@ -471,6 +471,21 @@ const ru: PartialLocaleType = {
FineTuned: {
Sysmessage: "Вы - помощник",
},
SearchChat: {
Name: "Поиск",
Page: {
Title: "Поиск в истории чатов",
Search: "Введите ключевые слова для поиска",
NoResult: "Результатов не найдено",
NoData: "Нет данных",
Loading: "Загрузка",
SubTitle: (count: number) => `Найдено ${count} результатов`,
},
Item: {
View: "Просмотр",
},
},
Mask: {
Name: "Маска",
Page: {

View File

@@ -423,6 +423,21 @@ const sk: PartialLocaleType = {
FineTuned: {
Sysmessage: "Ste asistent, ktorý",
},
SearchChat: {
Name: "Hľadať",
Page: {
Title: "Hľadať v histórii chatu",
Search: "Zadajte kľúčové slová na vyhľadávanie",
NoResult: "Nenašli sa žiadne výsledky",
NoData: "Žiadne údaje",
Loading: "Načítava sa",
SubTitle: (count: number) => `Nájdených ${count} výsledkov`,
},
Item: {
View: "Zobraziť",
},
},
Mask: {
Name: "Maska",
Page: {

View File

@@ -470,6 +470,21 @@ const tr: PartialLocaleType = {
FineTuned: {
Sysmessage: "Sen bir asistansın",
},
SearchChat: {
Name: "Ara",
Page: {
Title: "Sohbet geçmişini ara",
Search: "Arama anahtar kelimelerini girin",
NoResult: "Sonuç bulunamadı",
NoData: "Veri yok",
Loading: "Yükleniyor",
SubTitle: (count: number) => `${count} sonuç bulundu`,
},
Item: {
View: "Görüntüle",
},
},
Mask: {
Name: "Maske",
Page: {

View File

@@ -452,6 +452,21 @@ const tw = {
},
},
},
SearchChat: {
Name: "搜索",
Page: {
Title: "搜索聊天記錄",
Search: "輸入搜索關鍵詞",
NoResult: "沒有找到結果",
NoData: "沒有數據",
Loading: "加載中",
SubTitle: (count: number) => `找到 ${count} 條結果`,
},
Item: {
View: "查看",
},
},
NewChat: {
Return: "返回",
Skip: "跳過",

View File

@@ -466,6 +466,21 @@ const vi: PartialLocaleType = {
FineTuned: {
Sysmessage: "Bạn là một trợ lý",
},
SearchChat: {
Name: "Tìm kiếm",
Page: {
Title: "Tìm kiếm lịch sử trò chuyện",
Search: "Nhập từ khóa tìm kiếm",
NoResult: "Không tìm thấy kết quả",
NoData: "Không có dữ liệu",
Loading: "Đang tải",
SubTitle: (count: number) => `Tìm thấy ${count} kết quả`,
},
Item: {
View: "Xem",
},
},
Mask: {
Name: "Mặt nạ",
Page: {

View File

@@ -183,7 +183,7 @@ export const useAccessStore = createPersistStore(
this.isValidBaidu() ||
this.isValidByteDance() ||
this.isValidAlibaba() ||
this.isValidTencent ||
this.isValidTencent() ||
this.isValidMoonshot() ||
this.isValidIflytek() ||
!this.enabledAccessControl() ||

View File

@@ -27,6 +27,7 @@ import { createPersistStore } from "../utils/store";
import { collectModelsWithDefaultModel } from "../utils/model";
import { useAccessStore } from "./access";
import { isDalle3 } from "../utils";
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
export type ChatMessage = RequestMessage & {
date: string;
@@ -665,7 +666,8 @@ export const useChatStore = createPersistStore(
set(() => ({ sessions }));
},
clearAllData() {
async clearAllData() {
await indexedDBStorage.clear();
localStorage.clear();
location.reload();
},

View File

@@ -304,7 +304,7 @@ pre {
}
}
code{
pre {
.show-hide-button {
border-radius: 10px;
position: absolute;
@@ -314,7 +314,9 @@ code{
height: fit-content;
display: inline-flex;
justify-content: center;
pointer-events: none;
button{
pointer-events: auto;
margin-top: 3em;
margin-bottom: 4em;
padding: 5px 16px;

View File

@@ -8,7 +8,7 @@ export function useAllModels() {
const models = useMemo(() => {
return collectModelsWithDefaultModel(
configStore.models,
[configStore.customModels, accessStore.customModels].join(","),
[accessStore.customModels, configStore.customModels].join(","),
accessStore.defaultModel,
);
}, [

View File

@@ -0,0 +1,44 @@
import { StateStorage } from "zustand/middleware";
import { get, set, del, clear } from "idb-keyval";
class IndexedDBStorage implements StateStorage {
public async getItem(name: string): Promise<string | null> {
try {
const value = (await get(name)) || localStorage.getItem(name);
return value;
} catch (error) {
return localStorage.getItem(name);
}
}
public async setItem(name: string, value: string): Promise<void> {
try {
const _value = JSON.parse(value);
if (!_value?.state?._hasHydrated) {
console.warn("skip setItem", name);
return;
}
await set(name, value);
} catch (error) {
localStorage.setItem(name, value);
}
}
public async removeItem(name: string): Promise<void> {
try {
await del(name);
} catch (error) {
localStorage.removeItem(name);
}
}
public async clear(): Promise<void> {
try {
await clear();
} catch (error) {
localStorage.clear();
}
}
}
export const indexedDBStorage = new IndexedDBStorage();

View File

@@ -1,7 +1,8 @@
import { create } from "zustand";
import { combine, persist } from "zustand/middleware";
import { combine, persist, createJSONStorage } from "zustand/middleware";
import { Updater } from "../typing";
import { deepClone } from "./clone";
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
type SecondParam<T> = T extends (
_f: infer _F,
@@ -13,9 +14,11 @@ type SecondParam<T> = T extends (
type MakeUpdater<T> = {
lastUpdateTime: number;
_hasHydrated: boolean;
markUpdate: () => void;
update: Updater<T>;
setHasHydrated: (state: boolean) => void;
};
type SetStoreState<T> = (
@@ -31,12 +34,20 @@ export function createPersistStore<T extends object, M>(
) => M,
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
) {
persistOptions.storage = createJSONStorage(() => indexedDBStorage);
const oldOonRehydrateStorage = persistOptions?.onRehydrateStorage;
persistOptions.onRehydrateStorage = (state) => {
oldOonRehydrateStorage?.(state);
return () => state.setHasHydrated(true);
};
return create(
persist(
combine(
{
...state,
lastUpdateTime: 0,
_hasHydrated: false,
},
(set, get) => {
return {
@@ -55,6 +66,9 @@ export function createPersistStore<T extends object, M>(
lastUpdateTime: Date.now(),
});
},
setHasHydrated: (state: boolean) => {
set({ _hasHydrated: state } as Partial<T & M & MakeUpdater<T>>);
},
} as M & MakeUpdater<T>;
},
),

View File

@@ -28,6 +28,7 @@
"fuse.js": "^7.0.0",
"heic2any": "^0.0.4",
"html-to-image": "^1.11.11",
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
"mermaid": "^10.6.1",
"nanoid": "^5.0.3",

View File

@@ -3926,6 +3926,11 @@ iconv-lite@0.6:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
idb-keyval@^6.2.1:
version "6.2.1"
resolved "https://registry.npmmirror.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"
integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==
ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"