mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-02 08:06:38 +08:00
commit
1f48bd5f28
@ -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 { useParams } from "react-router";
|
||||||
import { useWindowSize } from "@/app/utils";
|
import { useWindowSize } from "@/app/utils";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
@ -8,6 +15,7 @@ import CopyIcon from "../icons/copy.svg";
|
|||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
import LoadingButtonIcon from "../icons/loading.svg";
|
import LoadingButtonIcon from "../icons/loading.svg";
|
||||||
|
import ReloadButtonIcon from "../icons/reload.svg";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { Modal, showToast } from "./ui-lib";
|
import { Modal, showToast } from "./ui-lib";
|
||||||
import { copyToClipboard, downloadAs } from "../utils";
|
import { copyToClipboard, downloadAs } from "../utils";
|
||||||
@ -15,14 +23,21 @@ import { Path, ApiPath, REPO_URL } from "@/app/constant";
|
|||||||
import { Loading } from "./home";
|
import { Loading } from "./home";
|
||||||
import styles from "./artifacts.module.scss";
|
import styles from "./artifacts.module.scss";
|
||||||
|
|
||||||
export function HTMLPreview(props: {
|
type HTMLPreviewProps = {
|
||||||
code: string;
|
code: string;
|
||||||
autoHeight?: boolean;
|
autoHeight?: boolean;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
onLoad?: (title?: string) => void;
|
onLoad?: (title?: string) => void;
|
||||||
}) {
|
};
|
||||||
const ref = useRef<HTMLIFrameElement>(null);
|
|
||||||
const frameId = useRef<string>(nanoid());
|
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 [iframeHeight, setIframeHeight] = useState(600);
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
/*
|
/*
|
||||||
@ -37,7 +52,7 @@ export function HTMLPreview(props: {
|
|||||||
const handleMessage = (e: any) => {
|
const handleMessage = (e: any) => {
|
||||||
const { id, height, title } = e.data;
|
const { id, height, title } = e.data;
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
if (id == frameId.current) {
|
if (id == frameId) {
|
||||||
setIframeHeight(height);
|
setIframeHeight(height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -45,7 +60,13 @@ export function HTMLPreview(props: {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("message", handleMessage);
|
window.removeEventListener("message", handleMessage);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [frameId]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
reload: () => {
|
||||||
|
setFrameId(nanoid());
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const height = useMemo(() => {
|
const height = useMemo(() => {
|
||||||
if (!props.autoHeight) return props.height || 600;
|
if (!props.autoHeight) return props.height || 600;
|
||||||
@ -53,16 +74,18 @@ export function HTMLPreview(props: {
|
|||||||
return props.height;
|
return props.height;
|
||||||
}
|
}
|
||||||
const parentHeight = props.height || 600;
|
const parentHeight = props.height || 600;
|
||||||
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
|
return iframeHeight + 40 > parentHeight
|
||||||
|
? parentHeight
|
||||||
|
: iframeHeight + 40;
|
||||||
}, [props.autoHeight, props.height, iframeHeight]);
|
}, [props.autoHeight, props.height, iframeHeight]);
|
||||||
|
|
||||||
const srcDoc = useMemo(() => {
|
const srcDoc = useMemo(() => {
|
||||||
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
||||||
if (props.code.includes("</head>")) {
|
if (props.code.includes("<!DOCTYPE html>")) {
|
||||||
props.code.replace("</head>", "</head>" + script);
|
props.code.replace("<!DOCTYPE html>", "<!DOCTYPE html>" + script);
|
||||||
}
|
}
|
||||||
return props.code + script;
|
return script + props.code;
|
||||||
}, [props.code]);
|
}, [props.code, frameId]);
|
||||||
|
|
||||||
const handleOnLoad = () => {
|
const handleOnLoad = () => {
|
||||||
if (props?.onLoad) {
|
if (props?.onLoad) {
|
||||||
@ -73,15 +96,16 @@ export function HTMLPreview(props: {
|
|||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
className={styles["artifacts-iframe"]}
|
className={styles["artifacts-iframe"]}
|
||||||
id={frameId.current}
|
key={frameId}
|
||||||
ref={ref}
|
ref={iframeRef}
|
||||||
sandbox="allow-forms allow-modals allow-scripts"
|
sandbox="allow-forms allow-modals allow-scripts"
|
||||||
style={{ height }}
|
style={{ height }}
|
||||||
srcDoc={srcDoc}
|
srcDoc={srcDoc}
|
||||||
onLoad={handleOnLoad}
|
onLoad={handleOnLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export function ArtifactsShareButton({
|
export function ArtifactsShareButton({
|
||||||
getCode,
|
getCode,
|
||||||
@ -184,6 +208,7 @@ export function Artifacts() {
|
|||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [fileName, setFileName] = useState("");
|
const [fileName, setFileName] = useState("");
|
||||||
|
const previewRef = useRef<HTMLPreviewHander>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
@ -208,6 +233,13 @@ export function Artifacts() {
|
|||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||||
<IconButton bordered icon={<GithubIcon />} shadow />
|
<IconButton bordered icon={<GithubIcon />} shadow />
|
||||||
</a>
|
</a>
|
||||||
|
<IconButton
|
||||||
|
bordered
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
icon={<ReloadButtonIcon />}
|
||||||
|
shadow
|
||||||
|
onClick={() => previewRef.current?.reload()}
|
||||||
|
/>
|
||||||
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
|
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
|
||||||
<ArtifactsShareButton
|
<ArtifactsShareButton
|
||||||
id={id}
|
id={id}
|
||||||
@ -220,6 +252,7 @@ export function Artifacts() {
|
|||||||
{code && (
|
{code && (
|
||||||
<HTMLPreview
|
<HTMLPreview
|
||||||
code={code}
|
code={code}
|
||||||
|
ref={previewRef}
|
||||||
autoHeight={false}
|
autoHeight={false}
|
||||||
height={"100%"}
|
height={"100%"}
|
||||||
onLoad={(title) => {
|
onLoad={(title) => {
|
||||||
|
@ -1480,7 +1480,7 @@ function _Chat() {
|
|||||||
color={"var(--second)"}
|
color={"var(--second)"}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
当天使用:
|
{Locale.Chat.UseTip}
|
||||||
<Progress
|
<Progress
|
||||||
percent={
|
percent={
|
||||||
(parseInt(localStorage.getItem("current_day_token") ?? "0") /
|
(parseInt(localStorage.getItem("current_day_token") ?? "0") /
|
||||||
|
@ -59,6 +59,13 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
|
|||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SearchChat = dynamic(
|
||||||
|
async () => (await import("./search-chat")).SearchChatPage,
|
||||||
|
{
|
||||||
|
loading: () => <Loading noLogo />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
@ -178,6 +185,7 @@ function Screen() {
|
|||||||
<Route path={Path.Home} element={<Chat />} />
|
<Route path={Path.Home} element={<Chat />} />
|
||||||
<Route path={Path.NewChat} element={<NewChat />} />
|
<Route path={Path.NewChat} element={<NewChat />} />
|
||||||
<Route path={Path.Masks} element={<MaskPage />} />
|
<Route path={Path.Masks} element={<MaskPage />} />
|
||||||
|
<Route path={Path.SearchChat} element={<SearchChat />} />
|
||||||
<Route path={Path.Chat} element={<Chat />} />
|
<Route path={Path.Chat} element={<Chat />} />
|
||||||
<Route path={Path.Settings} element={<Settings />} />
|
<Route path={Path.Settings} element={<Settings />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -8,14 +8,21 @@ import RehypeHighlight from "rehype-highlight";
|
|||||||
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
|
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
|
||||||
import { copyToClipboard, useWindowSize } from "../utils";
|
import { copyToClipboard, useWindowSize } from "../utils";
|
||||||
import mermaid from "mermaid";
|
import mermaid from "mermaid";
|
||||||
|
import Locale from "../locales";
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
|
import ReloadButtonIcon from "../icons/reload.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { showImageModal, FullScreen } from "./ui-lib";
|
import { showImageModal, FullScreen } from "./ui-lib";
|
||||||
import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
|
import {
|
||||||
|
ArtifactsShareButton,
|
||||||
|
HTMLPreview,
|
||||||
|
HTMLPreviewHander,
|
||||||
|
} from "./artifacts";
|
||||||
import { Plugin } from "../constant";
|
import { Plugin } from "../constant";
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
|
import { IconButton } from "./button";
|
||||||
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
@ -64,7 +71,7 @@ export function Mermaid(props: { code: string }) {
|
|||||||
|
|
||||||
export function PreCode(props: { children: any }) {
|
export function PreCode(props: { children: any }) {
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
const refText = ref.current?.innerText;
|
const previewRef = useRef<HTMLPreviewHander>(null);
|
||||||
const [mermaidCode, setMermaidCode] = useState("");
|
const [mermaidCode, setMermaidCode] = useState("");
|
||||||
const [htmlCode, setHtmlCode] = useState("");
|
const [htmlCode, setHtmlCode] = useState("");
|
||||||
const { height } = useWindowSize();
|
const { height } = useWindowSize();
|
||||||
@ -79,6 +86,7 @@ export function PreCode(props: { children: any }) {
|
|||||||
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
||||||
}
|
}
|
||||||
const htmlDom = ref.current.querySelector("code.language-html");
|
const htmlDom = ref.current.querySelector("code.language-html");
|
||||||
|
const refText = ref.current.querySelector("code")?.innerText;
|
||||||
if (htmlDom) {
|
if (htmlDom) {
|
||||||
setHtmlCode((htmlDom as HTMLElement).innerText);
|
setHtmlCode((htmlDom as HTMLElement).innerText);
|
||||||
} else if (refText?.startsWith("<!DOCTYPE")) {
|
} else if (refText?.startsWith("<!DOCTYPE")) {
|
||||||
@ -86,11 +94,6 @@ export function PreCode(props: { children: any }) {
|
|||||||
}
|
}
|
||||||
}, 600);
|
}, 600);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(renderArtifacts, 1);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [refText]);
|
|
||||||
|
|
||||||
const enableArtifacts = useMemo(
|
const enableArtifacts = useMemo(
|
||||||
() => plugins?.includes(Plugin.Artifacts),
|
() => plugins?.includes(Plugin.Artifacts),
|
||||||
[plugins],
|
[plugins],
|
||||||
@ -119,7 +122,9 @@ export function PreCode(props: { children: any }) {
|
|||||||
codeElement.style.whiteSpace = "pre-wrap";
|
codeElement.style.whiteSpace = "pre-wrap";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setTimeout(renderArtifacts, 1);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -145,7 +150,15 @@ export function PreCode(props: { children: any }) {
|
|||||||
style={{ position: "absolute", right: 20, top: 10 }}
|
style={{ position: "absolute", right: 20, top: 10 }}
|
||||||
getCode={() => htmlCode}
|
getCode={() => htmlCode}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
style={{ position: "absolute", right: 120, top: 10 }}
|
||||||
|
bordered
|
||||||
|
icon={<ReloadButtonIcon />}
|
||||||
|
shadow
|
||||||
|
onClick={() => previewRef.current?.reload()}
|
||||||
|
/>
|
||||||
<HTMLPreview
|
<HTMLPreview
|
||||||
|
ref={previewRef}
|
||||||
code={htmlCode}
|
code={htmlCode}
|
||||||
autoHeight={!document.fullscreenElement}
|
autoHeight={!document.fullscreenElement}
|
||||||
height={!document.fullscreenElement ? 600 : height}
|
height={!document.fullscreenElement ? 600 : height}
|
||||||
@ -182,16 +195,14 @@ function CustomCode(props: { children: any }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
</code>
|
||||||
{showToggle && collapsed && (
|
{showToggle && collapsed && (
|
||||||
<div
|
<div
|
||||||
className={`show-hide-button ${
|
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
|
||||||
collapsed ? "collapsed" : "expanded"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button onClick={toggleCollapsed}>查看全部</button>
|
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</code>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
168
app/components/search-chat.tsx
Normal file
168
app/components/search-chat.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
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;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
@ -1378,21 +1378,21 @@ export function Settings() {
|
|||||||
</Select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/*<ListItem title={Locale.Settings.Lang.Name}>*/}
|
<ListItem title={Locale.Settings.Lang.Name}>
|
||||||
{/* <Select*/}
|
<Select
|
||||||
{/* aria-label={Locale.Settings.Lang.Name}*/}
|
aria-label={Locale.Settings.Lang.Name}
|
||||||
{/* value={getLang()}*/}
|
value={getLang()}
|
||||||
{/* onChange={(e) => {*/}
|
onChange={(e) => {
|
||||||
{/* changeLang(e.target.value as any);*/}
|
changeLang(e.target.value as any);
|
||||||
{/* }}*/}
|
}}
|
||||||
{/* >*/}
|
>
|
||||||
{/* {AllLangs.map((lang) => (*/}
|
{AllLangs.map((lang) => (
|
||||||
{/* <option value={lang} key={lang}>*/}
|
<option value={lang} key={lang}>
|
||||||
{/* {ALL_LANG_OPTIONS[lang]}*/}
|
{ALL_LANG_OPTIONS[lang]}
|
||||||
{/* </option>*/}
|
</option>
|
||||||
{/* ))}*/}
|
))}
|
||||||
{/* </Select>*/}
|
</Select>
|
||||||
{/*</ListItem>*/}
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.FontSize.Title}
|
title={Locale.Settings.FontSize.Title}
|
||||||
|
@ -15,6 +15,7 @@ import DragIcon from "../icons/drag.svg";
|
|||||||
import DiscoveryIcon from "../icons/discovery.svg";
|
import DiscoveryIcon from "../icons/discovery.svg";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
import { getLang } from "../locales";
|
||||||
|
|
||||||
import { useAppConfig, useChatStore } from "../store";
|
import { useAppConfig, useChatStore } from "../store";
|
||||||
|
|
||||||
@ -248,15 +249,24 @@ export function SideBar(props: { className?: string }) {
|
|||||||
chatStore.currentSession().mask.modelConfig?.providerName ||
|
chatStore.currentSession().mask.modelConfig?.providerName ||
|
||||||
ServiceProvider.OpenAI;
|
ServiceProvider.OpenAI;
|
||||||
|
|
||||||
|
const lange = getLang();
|
||||||
|
const SideBarHeaderTextSubtitle: React.ReactNode = useMemo(() => {
|
||||||
|
if (lange === "en") {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{" "}
|
||||||
|
Choose Your Own Assistant
|
||||||
|
<br /> <br />
|
||||||
|
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
||||||
|
1. Sometimes it might act up a bit. Click <b>New Chat</b> below to try
|
||||||
|
again. <br /> 2. For drawing: Generate images with the format "/mj
|
||||||
|
prompt" (you can look up tools or methods for using MidJourney
|
||||||
|
prompts). <br /> 3. If you find it helpful, consider buying the author
|
||||||
|
a coffee.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<SideBarContainer
|
|
||||||
onDragStart={onDragStart}
|
|
||||||
shouldNarrow={shouldNarrow}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SideBarHeader
|
|
||||||
title="这里开始……"
|
|
||||||
subTitle={
|
|
||||||
<span>
|
<span>
|
||||||
选择一个你自己的助理
|
选择一个你自己的助理
|
||||||
<br />
|
<br />
|
||||||
@ -268,13 +278,24 @@ export function SideBar(props: { className?: string }) {
|
|||||||
<br />
|
<br />
|
||||||
3. 如果觉得还不错,可以给作者赏杯咖啡
|
3. 如果觉得还不错,可以给作者赏杯咖啡
|
||||||
</span>
|
</span>
|
||||||
}
|
);
|
||||||
|
}, [lange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideBarContainer
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
shouldNarrow={shouldNarrow}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SideBarHeader
|
||||||
|
title={Locale.SideBarHeader.Title}
|
||||||
|
subTitle={SideBarHeaderTextSubtitle}
|
||||||
logo={<ChatGptIcon />}
|
logo={<ChatGptIcon />}
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-header-bar"]}>
|
<div className={styles["sidebar-header-bar"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<CoffeeIcon />}
|
icon={<CoffeeIcon />}
|
||||||
text={shouldNarrow ? undefined : "赏杯咖啡️"}
|
text={shouldNarrow ? undefined : Locale.SideBarHeader.Coffee}
|
||||||
className={styles["sidebar-bar-button"]}
|
className={styles["sidebar-bar-button"]}
|
||||||
onClick={() => navigate(Path.Reward)}
|
onClick={() => navigate(Path.Reward)}
|
||||||
shadow
|
shadow
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export const OWNER = "Yidadaa";
|
import path from "path";
|
||||||
|
|
||||||
|
export const OWNER = "ChatGPTNextWeb";
|
||||||
export const REPO = "ChatGPT-Next-Web";
|
export const REPO = "ChatGPT-Next-Web";
|
||||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
||||||
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
||||||
@ -41,6 +43,7 @@ export enum Path {
|
|||||||
Sd = "/sd",
|
Sd = "/sd",
|
||||||
SdNew = "/sd-new",
|
SdNew = "/sd-new",
|
||||||
Artifacts = "/artifacts",
|
Artifacts = "/artifacts",
|
||||||
|
SearchChat = "/search-chat",
|
||||||
Reward = "/reward",
|
Reward = "/reward",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,10 +243,10 @@ export const GEMINI_SUMMARIZE_MODEL = "gemini-pro";
|
|||||||
|
|
||||||
export const KnowledgeCutOffDate: Record<string, string> = {
|
export const KnowledgeCutOffDate: Record<string, string> = {
|
||||||
default: "2021-09",
|
default: "2021-09",
|
||||||
"gpt-4o": "2023-10",
|
|
||||||
"gpt-4-turbo": "2023-12",
|
"gpt-4-turbo": "2023-12",
|
||||||
"gpt-4-turbo-2024-04-09": "2023-12",
|
"gpt-4-turbo-2024-04-09": "2023-12",
|
||||||
"gpt-4-turbo-preview": "2023-12",
|
"gpt-4-turbo-preview": "2023-12",
|
||||||
|
"gpt-4o": "2023-10",
|
||||||
"gpt-4o-2024-05-13": "2023-10",
|
"gpt-4o-2024-05-13": "2023-10",
|
||||||
"gpt-4o-2024-08-06": "2023-10",
|
"gpt-4o-2024-08-06": "2023-10",
|
||||||
"gpt-4o-mini": "2023-10",
|
"gpt-4o-mini": "2023-10",
|
||||||
@ -446,27 +449,6 @@ export const DEFAULT_MODELS = [
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// export const AZURE_MODELS: string[] = [
|
|
||||||
// //"gpt-35-turbo-0125",
|
|
||||||
// "gpt-4-turbo-2024-04-09",
|
|
||||||
// "gpt-4o",
|
|
||||||
// ];
|
|
||||||
// export const AZURE_PATH = AZURE_MODELS.map((m) => { m: `openai/deployments/${m}/chat/completions`});
|
|
||||||
// export const AZURE_PATH = AZURE_MODELS.map((m) => ({ m: `openai/deployments/${m}/chat/completions`} ));
|
|
||||||
// export const AZURE_PATH = AZURE_MODELS.reduce(
|
|
||||||
// (acc, item) => ({
|
|
||||||
// ...acc,
|
|
||||||
// [item]: `openai/deployments/${item}/chat/completions`,
|
|
||||||
// }),
|
|
||||||
// {},
|
|
||||||
// );
|
|
||||||
// console.log(AZURE_PATH);
|
|
||||||
|
|
||||||
export const DISABLE_MODELS = DEFAULT_MODELS.filter(
|
|
||||||
(item) => !item.available,
|
|
||||||
).map((item2) => item2.name);
|
|
||||||
|
|
||||||
// console.log('========', DISABLE_MODELS)
|
|
||||||
export const CHAT_PAGE_SIZE = 15;
|
export const CHAT_PAGE_SIZE = 15;
|
||||||
export const MAX_RENDER_MSG_COUNT = 45;
|
export const MAX_RENDER_MSG_COUNT = 45;
|
||||||
|
|
||||||
@ -484,4 +466,11 @@ export const internalAllowedWebDavEndpoints = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_GA_ID = "G-89WN60ZK2E";
|
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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DISABLE_MODELS = DEFAULT_MODELS.filter(
|
||||||
|
(item) => !item.available,
|
||||||
|
).map((item2) => item2.name);
|
||||||
|
1
app/icons/zoom.svg
Normal file
1
app/icons/zoom.svg
Normal 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 |
@ -58,10 +58,15 @@ const cn = {
|
|||||||
ImageAgentOpenTip:
|
ImageAgentOpenTip:
|
||||||
"开启之后,返回的Midjourney图片将会通过本程序自身代理,所以本程序需要处于可以访问cdn.discordapp.com的网络环境中才有效",
|
"开启之后,返回的Midjourney图片将会通过本程序自身代理,所以本程序需要处于可以访问cdn.discordapp.com的网络环境中才有效",
|
||||||
},
|
},
|
||||||
|
SideBarHeader: {
|
||||||
|
Title: "这里开始……",
|
||||||
|
Coffee: "赏杯咖啡",
|
||||||
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
|
UseTip: "当天使用:",
|
||||||
SubTitle: (count: number) => `共 ${count} 条对话`,
|
SubTitle: (count: number) => `共 ${count} 条对话`,
|
||||||
EditMessage: {
|
EditMessage: {
|
||||||
Title: "编辑消息记录",
|
Title: "编辑消息记录",
|
||||||
@ -560,6 +565,21 @@ const cn = {
|
|||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "你是一个助手",
|
Sysmessage: "你是一个助手",
|
||||||
},
|
},
|
||||||
|
SearchChat: {
|
||||||
|
Name: "搜索",
|
||||||
|
Page: {
|
||||||
|
Title: "搜索聊天记录",
|
||||||
|
Search: "输入搜索关键词",
|
||||||
|
NoResult: "没有找到结果",
|
||||||
|
NoData: "没有数据",
|
||||||
|
Loading: "加载中",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `搜索到 ${count} 条结果`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "查看",
|
||||||
|
},
|
||||||
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "面具",
|
Name: "面具",
|
||||||
Page: {
|
Page: {
|
||||||
|
@ -60,10 +60,15 @@ const en: LocaleType = {
|
|||||||
ImageAgentOpenTip:
|
ImageAgentOpenTip:
|
||||||
"After turning it on, the returned Midjourney image will be proxied by this program itself, so this program needs to be in a network environment that can access cdn.discordapp.com to be effective",
|
"After turning it on, the returned Midjourney image will be proxied by this program itself, so this program needs to be in a network environment that can access cdn.discordapp.com to be effective",
|
||||||
},
|
},
|
||||||
|
SideBarHeader: {
|
||||||
|
Title: "Start Here...",
|
||||||
|
Coffee: "Coffee please",
|
||||||
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} messages`,
|
ChatItemCount: (count: number) => `${count} messages`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
|
UseTip: "day use:",
|
||||||
SubTitle: (count: number) => `${count} messages`,
|
SubTitle: (count: number) => `${count} messages`,
|
||||||
EditMessage: {
|
EditMessage: {
|
||||||
Title: "Edit All Messages",
|
Title: "Edit All Messages",
|
||||||
@ -568,6 +573,21 @@ const en: LocaleType = {
|
|||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "You are an assistant that",
|
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: {
|
Mask: {
|
||||||
Name: "Mask",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
|
@ -452,6 +452,21 @@ const tw = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SearchChat: {
|
||||||
|
Name: "搜索",
|
||||||
|
Page: {
|
||||||
|
Title: "搜索聊天記錄",
|
||||||
|
Search: "輸入搜索關鍵詞",
|
||||||
|
NoResult: "沒有找到結果",
|
||||||
|
NoData: "沒有數據",
|
||||||
|
Loading: "加載中",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `找到 ${count} 條結果`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "查看",
|
||||||
|
},
|
||||||
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "返回",
|
Return: "返回",
|
||||||
Skip: "跳過",
|
Skip: "跳過",
|
||||||
|
@ -187,7 +187,7 @@ export const useAccessStore = createPersistStore(
|
|||||||
this.isValidBaidu() ||
|
this.isValidBaidu() ||
|
||||||
this.isValidByteDance() ||
|
this.isValidByteDance() ||
|
||||||
this.isValidAlibaba() ||
|
this.isValidAlibaba() ||
|
||||||
this.isValidTencent ||
|
this.isValidTencent() ||
|
||||||
this.isValidMoonshot() ||
|
this.isValidMoonshot() ||
|
||||||
this.isValidIflytek() ||
|
this.isValidIflytek() ||
|
||||||
!this.enabledAccessControl() ||
|
!this.enabledAccessControl() ||
|
||||||
|
@ -304,7 +304,7 @@ pre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code{
|
pre {
|
||||||
.show-hide-button {
|
.show-hide-button {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -314,7 +314,9 @@ code{
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
button{
|
button{
|
||||||
|
pointer-events: auto;
|
||||||
margin-top: 3em;
|
margin-top: 3em;
|
||||||
margin-bottom: 4em;
|
margin-bottom: 4em;
|
||||||
padding: 5px 16px;
|
padding: 5px 16px;
|
||||||
|
Loading…
Reference in New Issue
Block a user