mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-08 23:20:28 +08:00
feat: i18n refactor and style adjustment
This commit is contained in:
@@ -18,6 +18,15 @@
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
|
||||
path {
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shadow {
|
||||
|
@@ -5,10 +5,10 @@ import styles from "./button.module.scss";
|
||||
export function IconButton(props: {
|
||||
onClick?: () => void;
|
||||
icon?: JSX.Element;
|
||||
type?: "primary" | "danger";
|
||||
text?: string;
|
||||
bordered?: boolean;
|
||||
shadow?: boolean;
|
||||
noDark?: boolean;
|
||||
className?: string;
|
||||
title?: string;
|
||||
disabled?: boolean;
|
||||
@@ -19,7 +19,7 @@ export function IconButton(props: {
|
||||
styles["icon-button"] +
|
||||
` ${props.bordered && styles.border} ${props.shadow && styles.shadow} ${
|
||||
props.className ?? ""
|
||||
} clickable`
|
||||
} clickable ${styles[props.type ?? ""]}`
|
||||
}
|
||||
onClick={props.onClick}
|
||||
title={props.title}
|
||||
@@ -29,7 +29,8 @@ export function IconButton(props: {
|
||||
{props.icon && (
|
||||
<div
|
||||
className={
|
||||
styles["icon-button-icon"] + ` ${props.noDark && "no-dark"}`
|
||||
styles["icon-button-icon"] +
|
||||
` ${props.type === "primary" && "no-dark"}`
|
||||
}
|
||||
>
|
||||
{props.icon}
|
||||
|
@@ -14,6 +14,8 @@ import { useChatStore } from "../store";
|
||||
import Locale from "../locales";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Path } from "../constant";
|
||||
import { MaskAvatar } from "./mask";
|
||||
import { Mask } from "../store/mask";
|
||||
|
||||
export function ChatItem(props: {
|
||||
onClick?: () => void;
|
||||
@@ -25,6 +27,7 @@ export function ChatItem(props: {
|
||||
id: number;
|
||||
index: number;
|
||||
narrow?: boolean;
|
||||
mask: Mask;
|
||||
}) {
|
||||
return (
|
||||
<Draggable draggableId={`${props.id}`} index={props.index}>
|
||||
@@ -44,7 +47,7 @@ export function ChatItem(props: {
|
||||
{props.narrow ? (
|
||||
<div className={styles["chat-item-narrow"]}>
|
||||
<div className={styles["chat-item-avatar"] + " no-dark"}>
|
||||
<BotIcon></BotIcon>
|
||||
<MaskAvatar mask={props.mask} />
|
||||
</div>
|
||||
<div className={styles["chat-item-narrow-count"]}>
|
||||
{props.count}
|
||||
@@ -129,6 +132,7 @@ export function ChatList(props: { narrow?: boolean }) {
|
||||
}
|
||||
}}
|
||||
narrow={props.narrow}
|
||||
mask={item.mask}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
|
@@ -53,13 +53,16 @@ import { IconButton } from "./button";
|
||||
import styles from "./home.module.scss";
|
||||
import chatStyle from "./chat.module.scss";
|
||||
|
||||
import { Input, List, ListItem, Modal, Popover, showModal } from "./ui-lib";
|
||||
import { ListItem, Modal, showModal } from "./ui-lib";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Path } from "../constant";
|
||||
import { ModelConfigList } from "./model-config";
|
||||
import { Avatar, AvatarPicker } from "./emoji";
|
||||
import { MaskConfig } from "./mask";
|
||||
import { DEFAULT_MASK_ID, useMaskStore } from "../store/mask";
|
||||
import { Avatar } from "./emoji";
|
||||
import { MaskAvatar, MaskConfig } from "./mask";
|
||||
import {
|
||||
DEFAULT_MASK_AVATAR,
|
||||
DEFAULT_MASK_ID,
|
||||
useMaskStore,
|
||||
} from "../store/mask";
|
||||
|
||||
const Markdown = dynamic(
|
||||
async () => memo((await import("./markdown")).Markdown),
|
||||
@@ -668,10 +671,8 @@ export function Chat() {
|
||||
<div className={styles["chat-message-avatar"]}>
|
||||
{message.role === "user" ? (
|
||||
<Avatar avatar={config.avatar} />
|
||||
) : session.mask.id === DEFAULT_MASK_ID ? (
|
||||
<Avatar model={message.model ?? "gpt-3.5-turbo"} />
|
||||
) : (
|
||||
<Avatar avatar={session.mask.avatar} />
|
||||
<MaskAvatar mask={session.mask} />
|
||||
)}
|
||||
</div>
|
||||
{showTyping && (
|
||||
@@ -778,7 +779,7 @@ export function Chat() {
|
||||
icon={<SendWhiteIcon />}
|
||||
text={Locale.Chat.Send}
|
||||
className={styles["chat-input-send"]}
|
||||
noDark
|
||||
type="primary"
|
||||
onClick={onUserSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { IconButton } from "./button";
|
||||
import GithubIcon from "../icons/github.svg";
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
import { ISSUE_URL, StoreKey } from "../constant";
|
||||
import { ISSUE_URL } from "../constant";
|
||||
import Locale from "../locales";
|
||||
import { downloadAs } from "../utils";
|
||||
|
||||
@@ -24,22 +24,15 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
|
||||
}
|
||||
|
||||
clearAndSaveData() {
|
||||
const snapshot: Record<string, any> = {};
|
||||
Object.values(StoreKey).forEach((key) => {
|
||||
snapshot[key] = localStorage.getItem(key);
|
||||
|
||||
if (snapshot[key]) {
|
||||
try {
|
||||
snapshot[key] = JSON.parse(snapshot[key]);
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
downloadAs(JSON.stringify(snapshot), "chatgpt-next-web-snapshot.json");
|
||||
} catch {}
|
||||
|
||||
localStorage.clear();
|
||||
downloadAs(
|
||||
JSON.stringify(localStorage),
|
||||
"chatgpt-next-web-snapshot.json",
|
||||
);
|
||||
} finally {
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -65,7 +58,8 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
|
||||
icon={<ResetIcon />}
|
||||
text="Clear All Data"
|
||||
onClick={() =>
|
||||
confirm(Locale.Store.ConfirmClearAll) && this.clearAndSaveData()
|
||||
confirm(Locale.Settings.Actions.ConfirmClearAll) &&
|
||||
this.clearAndSaveData()
|
||||
}
|
||||
bordered
|
||||
/>
|
||||
|
@@ -269,7 +269,7 @@
|
||||
.chat-item-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
opacity: 0.1;
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
transform: scale(4);
|
||||
}
|
||||
|
@@ -52,10 +52,18 @@
|
||||
animation: slide-in ease 0.4s;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
.actions {
|
||||
margin-top: 5vh;
|
||||
margin-bottom: 5vh;
|
||||
animation: slide-in ease 0.45s;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.search-bar {
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
width: 40vw;
|
||||
}
|
||||
}
|
||||
|
||||
.masks {
|
||||
|
@@ -3,11 +3,14 @@ import { Path, SlotID } from "../constant";
|
||||
import { IconButton } from "./button";
|
||||
import { EmojiAvatar } from "./emoji";
|
||||
import styles from "./new-chat.module.scss";
|
||||
|
||||
import LeftIcon from "../icons/left.svg";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import AddIcon from "../icons/lightning.svg";
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createEmptyMask, Mask, useMaskStore } from "../store/mask";
|
||||
import Locale from "../locales";
|
||||
import { useChatStore } from "../store";
|
||||
import { useAppConfig, useChatStore } from "../store";
|
||||
import { MaskAvatar } from "./mask";
|
||||
|
||||
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
||||
@@ -93,10 +96,14 @@ function useMaskGroup(masks: Mask[]) {
|
||||
export function NewChat() {
|
||||
const chatStore = useChatStore();
|
||||
const maskStore = useMaskStore();
|
||||
|
||||
const masks = maskStore.getAll();
|
||||
const groups = useMaskGroup(masks);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const config = useAppConfig();
|
||||
|
||||
const { state } = useLocation();
|
||||
|
||||
const startChat = (mask?: Mask) => {
|
||||
chatStore.newSession(mask);
|
||||
@@ -111,10 +118,19 @@ export function NewChat() {
|
||||
text={Locale.NewChat.Return}
|
||||
onClick={() => navigate(Path.Home)}
|
||||
></IconButton>
|
||||
<IconButton
|
||||
text={Locale.NewChat.Skip}
|
||||
onClick={() => startChat()}
|
||||
></IconButton>
|
||||
{!state?.fromHome && (
|
||||
<IconButton
|
||||
text={Locale.NewChat.NotShow}
|
||||
onClick={() => {
|
||||
if (confirm(Locale.NewChat.ConfirmNoShow)) {
|
||||
startChat();
|
||||
config.update(
|
||||
(config) => (config.dontShowMaskSplashScreen = true),
|
||||
);
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles["mask-cards"]}>
|
||||
<div className={styles["mask-card"]}>
|
||||
@@ -131,12 +147,22 @@ export function NewChat() {
|
||||
<div className={styles["title"]}>{Locale.NewChat.Title}</div>
|
||||
<div className={styles["sub-title"]}>{Locale.NewChat.SubTitle}</div>
|
||||
|
||||
<input
|
||||
className={styles["search-bar"]}
|
||||
placeholder={Locale.NewChat.More}
|
||||
type="text"
|
||||
onClick={() => navigate(Path.Masks)}
|
||||
/>
|
||||
<div className={styles["actions"]}>
|
||||
<input
|
||||
className={styles["search-bar"]}
|
||||
placeholder={Locale.NewChat.More}
|
||||
type="text"
|
||||
onClick={() => navigate(Path.Masks)}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
text={Locale.NewChat.Skip}
|
||||
onClick={() => startChat()}
|
||||
icon={<AddIcon />}
|
||||
type="primary"
|
||||
shadow
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles["masks"]}>
|
||||
{groups.map((masks, i) => (
|
||||
|
@@ -137,10 +137,7 @@ export function Settings() {
|
||||
const config = useAppConfig();
|
||||
const updateConfig = config.update;
|
||||
const resetConfig = config.reset;
|
||||
const [clearAllData, clearSessions] = useChatStore((state) => [
|
||||
state.clearAllData,
|
||||
state.clearSessions,
|
||||
]);
|
||||
const chatStore = useChatStore();
|
||||
|
||||
const updateStore = useUpdateStore();
|
||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||
@@ -160,9 +157,9 @@ export function Settings() {
|
||||
subscription: updateStore.subscription,
|
||||
};
|
||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||
function checkUsage() {
|
||||
function checkUsage(force = false) {
|
||||
setLoadingUsage(true);
|
||||
updateStore.updateUsage().finally(() => {
|
||||
updateStore.updateUsage(force).finally(() => {
|
||||
setLoadingUsage(false);
|
||||
});
|
||||
}
|
||||
@@ -216,11 +213,8 @@ export function Settings() {
|
||||
<IconButton
|
||||
icon={<ClearIcon />}
|
||||
onClick={() => {
|
||||
const confirmed = window.confirm(
|
||||
`${Locale.Settings.Actions.ConfirmClearAll.Confirm}`,
|
||||
);
|
||||
if (confirmed) {
|
||||
clearSessions();
|
||||
if (confirm(Locale.Settings.Actions.ConfirmClearAll)) {
|
||||
chatStore.clearAllData();
|
||||
}
|
||||
}}
|
||||
bordered
|
||||
@@ -231,10 +225,7 @@ export function Settings() {
|
||||
<IconButton
|
||||
icon={<ResetIcon />}
|
||||
onClick={() => {
|
||||
const confirmed = window.confirm(
|
||||
`${Locale.Settings.Actions.ConfirmResetAll.Confirm}`,
|
||||
);
|
||||
if (confirmed) {
|
||||
if (confirm(Locale.Settings.Actions.ConfirmResetAll)) {
|
||||
resetConfig();
|
||||
}
|
||||
}}
|
||||
@@ -370,19 +361,10 @@ export function Settings() {
|
||||
></InputRange>
|
||||
</ListItem>
|
||||
|
||||
<ListItem title={Locale.Settings.TightBorder}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.tightBorder}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.tightBorder = e.currentTarget.checked),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem title={Locale.Settings.SendPreviewBubble}>
|
||||
<ListItem
|
||||
title={Locale.Settings.SendPreviewBubble.Title}
|
||||
subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sendPreviewBubble}
|
||||
@@ -394,6 +376,23 @@ export function Settings() {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
title={Locale.Settings.Mask.Title}
|
||||
subTitle={Locale.Settings.Mask.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!config.dontShowMaskSplashScreen}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.dontShowMaskSplashScreen =
|
||||
!e.currentTarget.checked),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
@@ -448,7 +447,7 @@ export function Settings() {
|
||||
<IconButton
|
||||
icon={<ResetIcon></ResetIcon>}
|
||||
text={Locale.Settings.Usage.Check}
|
||||
onClick={checkUsage}
|
||||
onClick={() => checkUsage(true)}
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
|
@@ -87,6 +87,8 @@ export function SideBar(props: { className?: string }) {
|
||||
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const config = useAppConfig();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.sidebar} ${props.className} ${
|
||||
@@ -106,14 +108,14 @@ export function SideBar(props: { className?: string }) {
|
||||
<div className={styles["sidebar-header-bar"]}>
|
||||
<IconButton
|
||||
icon={<MaskIcon />}
|
||||
text="Mask"
|
||||
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
||||
className={styles["sidebar-bar-button"]}
|
||||
onClick={() => navigate(Path.Masks)}
|
||||
onClick={() => navigate(Path.NewChat, { state: { fromHome: true } })}
|
||||
shadow
|
||||
/>
|
||||
<IconButton
|
||||
icon={<PluginIcon />}
|
||||
text="Plugins"
|
||||
text={shouldNarrow ? undefined : Locale.Plugin.Name}
|
||||
className={styles["sidebar-bar-button"]}
|
||||
onClick={() => showToast(Locale.WIP)}
|
||||
shadow
|
||||
@@ -155,7 +157,11 @@ export function SideBar(props: { className?: string }) {
|
||||
icon={<AddIcon />}
|
||||
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
||||
onClick={() => {
|
||||
navigate(Path.NewChat);
|
||||
if (config.dontShowMaskSplashScreen) {
|
||||
chatStore.newSession();
|
||||
} else {
|
||||
navigate(Path.NewChat);
|
||||
}
|
||||
}}
|
||||
shadow
|
||||
/>
|
||||
|
Reference in New Issue
Block a user