mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-16 05:53:42 +08:00
Merge branch 'Yidadaa:main' into main
This commit is contained in:
@@ -15,7 +15,8 @@ export function AuthPage() {
|
||||
const access = useAccessStore();
|
||||
|
||||
const goHome = () => navigate(Path.Home);
|
||||
const resetAccessCode = () => access.updateCode(""); // Reset access code to empty string
|
||||
const goChat = () => navigate(Path.Chat);
|
||||
const resetAccessCode = () => { access.updateCode(""); access.updateToken(""); }; // Reset access code to empty string
|
||||
|
||||
useEffect(() => {
|
||||
if (getClientConfig()?.isApp) {
|
||||
@@ -42,17 +43,34 @@ export function AuthPage() {
|
||||
access.updateCode(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
{!access.hideUserApiKey ? (
|
||||
<>
|
||||
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
|
||||
<input
|
||||
className={styles["auth-input"]}
|
||||
type="password"
|
||||
placeholder={Locale.Settings.Token.Placeholder}
|
||||
value={access.token}
|
||||
onChange={(e) => {
|
||||
access.updateToken(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div className={styles["auth-actions"]}>
|
||||
<IconButton
|
||||
text={Locale.Auth.Confirm}
|
||||
type="primary"
|
||||
onClick={goHome}
|
||||
onClick={goChat}
|
||||
/>
|
||||
<IconButton
|
||||
text={Locale.Auth.Later}
|
||||
onClick={() => {
|
||||
resetAccessCode();
|
||||
goHome();
|
||||
}}
|
||||
/>
|
||||
<IconButton text={Locale.Auth.Later} onClick={() => {
|
||||
resetAccessCode();
|
||||
goHome();
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -937,7 +937,7 @@ function _Chat() {
|
||||
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
|
||||
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
|
||||
const isHitBottom =
|
||||
bottomHeight >= e.scrollHeight - (isMobileScreen ? 0 : 10);
|
||||
bottomHeight >= e.scrollHeight - (isMobileScreen ? 4 : 10);
|
||||
|
||||
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
|
||||
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
|
||||
@@ -1155,7 +1155,13 @@ function _Chat() {
|
||||
{isUser ? (
|
||||
<Avatar avatar={config.avatar} />
|
||||
) : (
|
||||
<MaskAvatar mask={session.mask} />
|
||||
<>
|
||||
{["system"].includes(message.role) ? (
|
||||
<Avatar avatar="2699-fe0f" />
|
||||
) : (
|
||||
<MaskAvatar mask={session.mask} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -433,25 +433,55 @@ export function ImagePreviewer(props: {
|
||||
|
||||
const isMobile = useMobileScreen();
|
||||
|
||||
const download = () => {
|
||||
const download = async () => {
|
||||
showToast(Locale.Export.Image.Toast);
|
||||
const dom = previewRef.current;
|
||||
if (!dom) return;
|
||||
toPng(dom)
|
||||
.then((blob) => {
|
||||
if (!blob) return;
|
||||
|
||||
if (isMobile || getClientConfig()?.isApp) {
|
||||
showImageModal(blob);
|
||||
|
||||
const isApp = getClientConfig()?.isApp;
|
||||
|
||||
try {
|
||||
const blob = await toPng(dom);
|
||||
if (!blob) return;
|
||||
|
||||
if (isMobile || (isApp && window.__TAURI__)) {
|
||||
if (isApp && window.__TAURI__) {
|
||||
const result = await window.__TAURI__.dialog.save({
|
||||
defaultPath: `${props.topic}.png`,
|
||||
filters: [
|
||||
{
|
||||
name: "PNG Files",
|
||||
extensions: ["png"],
|
||||
},
|
||||
{
|
||||
name: "All Files",
|
||||
extensions: ["*"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (result !== null) {
|
||||
const response = await fetch(blob);
|
||||
const buffer = await response.arrayBuffer();
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
|
||||
showToast(Locale.Download.Success);
|
||||
} else {
|
||||
showToast(Locale.Download.Failed);
|
||||
}
|
||||
} else {
|
||||
const link = document.createElement("a");
|
||||
link.download = `${props.topic}.png`;
|
||||
link.href = blob;
|
||||
link.click();
|
||||
refreshPreview();
|
||||
showImageModal(blob);
|
||||
}
|
||||
})
|
||||
.catch((e) => console.log("[Export Image] ", e));
|
||||
} else {
|
||||
const link = document.createElement("a");
|
||||
link.download = `${props.topic}.png`;
|
||||
link.href = blob;
|
||||
link.click();
|
||||
refreshPreview();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(Locale.Download.Failed);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshPreview = () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
color: var(--black);
|
||||
background-color: var(--white);
|
||||
min-width: 600px;
|
||||
min-height: 480px;
|
||||
min-height: 370px;
|
||||
max-width: 1200px;
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -115,7 +115,10 @@ const loadAsyncGoogleFont = () => {
|
||||
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
||||
linkEl.rel = "stylesheet";
|
||||
linkEl.href =
|
||||
googleFontUrl + "/css2?family=Noto+Sans:wght@300;400;700;900&display=swap";
|
||||
googleFontUrl +
|
||||
"/css2?family=" +
|
||||
encodeURIComponent("Noto Sans:wght@300;400;700;900") +
|
||||
"&display=swap";
|
||||
document.head.appendChild(linkEl);
|
||||
};
|
||||
|
||||
@@ -125,6 +128,7 @@ function Screen() {
|
||||
const isHome = location.pathname === Path.Home;
|
||||
const isAuth = location.pathname === Path.Auth;
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const shouldTightBorder = getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
||||
|
||||
useEffect(() => {
|
||||
loadAsyncGoogleFont();
|
||||
@@ -134,11 +138,9 @@ function Screen() {
|
||||
<div
|
||||
className={
|
||||
styles.container +
|
||||
` ${
|
||||
config.tightBorder && !isMobileScreen
|
||||
? styles["tight-container"]
|
||||
: styles.container
|
||||
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`
|
||||
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
|
||||
getLang() === "ar" ? styles["rtl-screen"] : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
{isAuth ? (
|
||||
|
||||
@@ -151,6 +151,7 @@ export function Markdown(
|
||||
ref={mdRef}
|
||||
onContextMenu={props.onContextMenu}
|
||||
onDoubleClickCapture={props.onDoubleClickCapture}
|
||||
dir="auto"
|
||||
>
|
||||
{props.loading ? (
|
||||
<LoadingIcon />
|
||||
|
||||
@@ -393,11 +393,13 @@ export function MaskPage() {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const masks = searchText.length > 0 ? searchMasks : allMasks;
|
||||
|
||||
// simple search, will refactor later
|
||||
// refactored already, now it accurate
|
||||
const onSearch = (text: string) => {
|
||||
setSearchText(text);
|
||||
if (text.length > 0) {
|
||||
const result = allMasks.filter((m) => m.name.includes(text));
|
||||
const result = allMasks.filter((m) =>
|
||||
m.name.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
setSearchMasks(result);
|
||||
} else {
|
||||
setSearchMasks(allMasks);
|
||||
|
||||
@@ -50,7 +50,7 @@ import Locale, {
|
||||
} from "../locales";
|
||||
import { copyToClipboard } from "../utils";
|
||||
import Link from "next/link";
|
||||
import { Path, RELEASE_URL, UPDATE_URL } from "../constant";
|
||||
import { Path, RELEASE_URL, STORAGE_KEY, UPDATE_URL } from "../constant";
|
||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||
import { ErrorBoundary } from "./error";
|
||||
import { InputRange } from "./input-range";
|
||||
@@ -275,7 +275,7 @@ function CheckButton() {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
text="检查可用性"
|
||||
text={Locale.Settings.Sync.Config.Modal.Check}
|
||||
bordered
|
||||
onClick={check}
|
||||
icon={
|
||||
@@ -413,7 +413,42 @@ function SyncConfigModal(props: { onClose?: () => void }) {
|
||||
|
||||
{syncStore.provider === ProviderType.UpStash && (
|
||||
<List>
|
||||
<ListItem title={Locale.WIP}></ListItem>
|
||||
<ListItem title={Locale.Settings.Sync.Config.UpStash.Endpoint}>
|
||||
<input
|
||||
type="text"
|
||||
value={syncStore.upstash.endpoint}
|
||||
onChange={(e) => {
|
||||
syncStore.update(
|
||||
(config) =>
|
||||
(config.upstash.endpoint = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem title={Locale.Settings.Sync.Config.UpStash.UserName}>
|
||||
<input
|
||||
type="text"
|
||||
value={syncStore.upstash.username}
|
||||
placeholder={STORAGE_KEY}
|
||||
onChange={(e) => {
|
||||
syncStore.update(
|
||||
(config) =>
|
||||
(config.upstash.username = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
></input>
|
||||
</ListItem>
|
||||
<ListItem title={Locale.Settings.Sync.Config.UpStash.Password}>
|
||||
<PasswordInput
|
||||
value={syncStore.upstash.apiKey}
|
||||
onChange={(e) => {
|
||||
syncStore.update(
|
||||
(config) => (config.upstash.apiKey = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
></PasswordInput>
|
||||
</ListItem>
|
||||
</List>
|
||||
)}
|
||||
</Modal>
|
||||
@@ -718,7 +753,7 @@ export function Settings() {
|
||||
title={`${config.fontSize ?? 14}px`}
|
||||
value={config.fontSize}
|
||||
min="12"
|
||||
max="18"
|
||||
max="40"
|
||||
step="1"
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
|
||||
import styles from "./home.module.scss";
|
||||
|
||||
@@ -17,6 +17,7 @@ import Locale from "../locales";
|
||||
import { useAppConfig, useChatStore } from "../store";
|
||||
|
||||
import {
|
||||
DEFAULT_SIDEBAR_WIDTH,
|
||||
MAX_SIDEBAR_WIDTH,
|
||||
MIN_SIDEBAR_WIDTH,
|
||||
NARROW_SIDEBAR_WIDTH,
|
||||
@@ -57,31 +58,57 @@ function useDragSideBar() {
|
||||
|
||||
const config = useAppConfig();
|
||||
const startX = useRef(0);
|
||||
const startDragWidth = useRef(config.sidebarWidth ?? 300);
|
||||
const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
|
||||
const lastUpdateTime = useRef(Date.now());
|
||||
|
||||
const handleMouseMove = useRef((e: MouseEvent) => {
|
||||
if (Date.now() < lastUpdateTime.current + 50) {
|
||||
return;
|
||||
}
|
||||
lastUpdateTime.current = Date.now();
|
||||
const d = e.clientX - startX.current;
|
||||
const nextWidth = limit(startDragWidth.current + d);
|
||||
config.update((config) => (config.sidebarWidth = nextWidth));
|
||||
});
|
||||
|
||||
const handleMouseUp = useRef(() => {
|
||||
startDragWidth.current = config.sidebarWidth ?? 300;
|
||||
window.removeEventListener("mousemove", handleMouseMove.current);
|
||||
window.removeEventListener("mouseup", handleMouseUp.current);
|
||||
});
|
||||
|
||||
const onDragMouseDown = (e: MouseEvent) => {
|
||||
startX.current = e.clientX;
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove.current);
|
||||
window.addEventListener("mouseup", handleMouseUp.current);
|
||||
const toggleSideBar = () => {
|
||||
config.update((config) => {
|
||||
if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) {
|
||||
config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;
|
||||
} else {
|
||||
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onDragStart = (e: MouseEvent) => {
|
||||
// Remembers the initial width each time the mouse is pressed
|
||||
startX.current = e.clientX;
|
||||
startDragWidth.current = config.sidebarWidth;
|
||||
const dragStartTime = Date.now();
|
||||
|
||||
const handleDragMove = (e: MouseEvent) => {
|
||||
if (Date.now() < lastUpdateTime.current + 20) {
|
||||
return;
|
||||
}
|
||||
lastUpdateTime.current = Date.now();
|
||||
const d = e.clientX - startX.current;
|
||||
const nextWidth = limit(startDragWidth.current + d);
|
||||
config.update((config) => {
|
||||
if (nextWidth < MIN_SIDEBAR_WIDTH) {
|
||||
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
|
||||
} else {
|
||||
config.sidebarWidth = nextWidth;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
// In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth
|
||||
window.removeEventListener("pointermove", handleDragMove);
|
||||
window.removeEventListener("pointerup", handleDragEnd);
|
||||
|
||||
// if user click the drag icon, should toggle the sidebar
|
||||
const shouldFireClick = Date.now() - dragStartTime < 300;
|
||||
if (shouldFireClick) {
|
||||
toggleSideBar();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", handleDragMove);
|
||||
window.addEventListener("pointerup", handleDragEnd);
|
||||
};
|
||||
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const shouldNarrow =
|
||||
!isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
|
||||
@@ -89,13 +116,13 @@ function useDragSideBar() {
|
||||
useEffect(() => {
|
||||
const barWidth = shouldNarrow
|
||||
? NARROW_SIDEBAR_WIDTH
|
||||
: limit(config.sidebarWidth ?? 300);
|
||||
: limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
|
||||
const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
|
||||
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
|
||||
}, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
|
||||
|
||||
return {
|
||||
onDragMouseDown,
|
||||
onDragStart,
|
||||
shouldNarrow,
|
||||
};
|
||||
}
|
||||
@@ -104,7 +131,7 @@ export function SideBar(props: { className?: string }) {
|
||||
const chatStore = useChatStore();
|
||||
|
||||
// drag side bar
|
||||
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
|
||||
const { onDragStart, shouldNarrow } = useDragSideBar();
|
||||
const navigate = useNavigate();
|
||||
const config = useAppConfig();
|
||||
|
||||
@@ -133,7 +160,13 @@ export function SideBar(props: { className?: string }) {
|
||||
icon={<MaskIcon />}
|
||||
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
||||
className={styles["sidebar-bar-button"]}
|
||||
onClick={() => navigate(Path.NewChat, { state: { fromHome: true } })}
|
||||
onClick={() => {
|
||||
if (config.dontShowMaskSplashScreen !== true) {
|
||||
navigate(Path.NewChat, { state: { fromHome: true } });
|
||||
} else {
|
||||
navigate(Path.Masks, { state: { fromHome: true } });
|
||||
}
|
||||
}}
|
||||
shadow
|
||||
/>
|
||||
<IconButton
|
||||
@@ -198,7 +231,7 @@ export function SideBar(props: { className?: string }) {
|
||||
|
||||
<div
|
||||
className={styles["sidebar-drag"]}
|
||||
onMouseDown={(e) => onDragMouseDown(e as any)}
|
||||
onPointerDown={(e) => onDragStart(e as any)}
|
||||
>
|
||||
<DragIcon />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user