Merge pull request #152 from sijinhui/merge

Merge
This commit is contained in:
sijinhui 2024-08-22 09:59:04 +08:00 committed by GitHub
commit 1f48bd5f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 422 additions and 134 deletions

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 { 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,73 +23,89 @@ 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());
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(() => { export type HTMLPreviewHander = {
const handleMessage = (e: any) => { reload: () => void;
const { id, height, title } = e.data; };
setTitle(title);
if (id == frameId.current) { export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
setIframeHeight(height); 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(() => { return (
if (!props.autoHeight) return props.height || 600; <iframe
if (typeof props.height === "string") { className={styles["artifacts-iframe"]}
return props.height; key={frameId}
} ref={iframeRef}
const parentHeight = props.height || 600; sandbox="allow-forms allow-modals allow-scripts"
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40; style={{ height }}
}, [props.autoHeight, props.height, iframeHeight]); srcDoc={srcDoc}
onLoad={handleOnLoad}
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}
/>
);
}
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) => {

View File

@ -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") /

View File

@ -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>

View File

@ -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}
{showToggle && collapsed && (
<div
className={`show-hide-button ${
collapsed ? "collapsed" : "expanded"
}`}
>
<button onClick={toggleCollapsed}></button>
</div>
)}
</code> </code>
{showToggle && collapsed && (
<div
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
>
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
</div>
)}
</> </>
); );
} }

View 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>
);
}

View File

@ -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}

View File

@ -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,6 +249,38 @@ 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 (
<span>
<br />
<br />
1. <b></b><b></b>
<br />
2. /mj
midjourney的提示词工具或使用方法
<br />
3.
</span>
);
}, [lange]);
return ( return (
<SideBarContainer <SideBarContainer
onDragStart={onDragStart} onDragStart={onDragStart}
@ -255,26 +288,14 @@ export function SideBar(props: { className?: string }) {
{...props} {...props}
> >
<SideBarHeader <SideBarHeader
title="这里开始……" title={Locale.SideBarHeader.Title}
subTitle={ subTitle={SideBarHeaderTextSubtitle}
<span>
<br />
<br />
1. <b></b><b></b>
<br />
2. /mj
midjourney的提示词工具或使用方法
<br />
3.
</span>
}
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

View File

@ -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
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

@ -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: {

View File

@ -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: {

View File

@ -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: "跳過",

View File

@ -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() ||

View File

@ -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;