forked from XiaoMo/ChatGPT-Next-Web
Merge pull request #659 from Yidadaa/bugfix-0409
fix: many UI bugs and resizable side bar
This commit is contained in:
commit
601e72b56c
@ -49,4 +49,7 @@
|
|||||||
.icon-button-text {
|
.icon-button-text {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ export function ChatList() {
|
|||||||
index={i}
|
index={i}
|
||||||
selected={i === selectedIndex}
|
selected={i === selectedIndex}
|
||||||
onClick={() => selectSession(i)}
|
onClick={() => selectSession(i)}
|
||||||
onDelete={chatStore.deleteSession}
|
onDelete={() => chatStore.deleteSession(i)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
@ -3,7 +3,7 @@ import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
|
|||||||
|
|
||||||
import SendWhiteIcon from "../icons/send-white.svg";
|
import SendWhiteIcon from "../icons/send-white.svg";
|
||||||
import BrainIcon from "../icons/brain.svg";
|
import BrainIcon from "../icons/brain.svg";
|
||||||
import ExportIcon from "../icons/export.svg";
|
import ExportIcon from "../icons/share.svg";
|
||||||
import ReturnIcon from "../icons/return.svg";
|
import ReturnIcon from "../icons/return.svg";
|
||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
@ -11,6 +11,8 @@ import LoadingIcon from "../icons/three-dots.svg";
|
|||||||
import BotIcon from "../icons/bot.svg";
|
import BotIcon from "../icons/bot.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
|
import MaxIcon from "../icons/max.svg";
|
||||||
|
import MinIcon from "../icons/min.svg";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Message,
|
Message,
|
||||||
@ -19,6 +21,7 @@ import {
|
|||||||
BOT_HELLO,
|
BOT_HELLO,
|
||||||
ROLES,
|
ROLES,
|
||||||
createMessage,
|
createMessage,
|
||||||
|
useAccessStore,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -485,11 +488,17 @@ export function Chat(props: {
|
|||||||
|
|
||||||
const context: RenderMessage[] = session.context.slice();
|
const context: RenderMessage[] = session.context.slice();
|
||||||
|
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
context.length === 0 &&
|
context.length === 0 &&
|
||||||
session.messages.at(0)?.content !== BOT_HELLO.content
|
session.messages.at(0)?.content !== BOT_HELLO.content
|
||||||
) {
|
) {
|
||||||
context.push(BOT_HELLO);
|
const copiedHello = Object.assign({}, BOT_HELLO);
|
||||||
|
if (!accessStore.isAuthorized()) {
|
||||||
|
copiedHello.content = Locale.Error.Unauthorized;
|
||||||
|
}
|
||||||
|
context.push(copiedHello);
|
||||||
}
|
}
|
||||||
|
|
||||||
// preview messages
|
// preview messages
|
||||||
@ -584,6 +593,17 @@ export function Chat(props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles["window-action-button"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={chatStore.config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
||||||
|
bordered
|
||||||
|
onClick={() => {
|
||||||
|
chatStore.updateConfig(
|
||||||
|
(config) => (config.tightBorder = !config.tightBorder),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PromptToast
|
<PromptToast
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
min-height: 480px;
|
min-height: 480px;
|
||||||
max-width: 900px;
|
max-width: 1200px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -48,6 +48,27 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
transition: width ease 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-drag {
|
||||||
|
$width: 10px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: $width;
|
||||||
|
background-color: var(--black);
|
||||||
|
cursor: ew-resize;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.window-content {
|
.window-content {
|
||||||
@ -177,10 +198,11 @@
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item-count {
|
.chat-item-count,
|
||||||
}
|
|
||||||
|
|
||||||
.chat-item-date {
|
.chat-item-date {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tail {
|
.sidebar-tail {
|
||||||
@ -436,6 +458,7 @@
|
|||||||
|
|
||||||
.export-content {
|
.export-content {
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
|
padding: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-content {
|
.loading-content {
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
require("../polyfill");
|
require("../polyfill");
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
MouseEventHandler,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
@ -24,6 +30,7 @@ import { Chat } from "./chat";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { REPO_URL } from "../constant";
|
import { REPO_URL } from "../constant";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
|
import { useDebounce } from "use-debounce";
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
@ -75,6 +82,49 @@ function useSwitchTheme() {
|
|||||||
}, [config.theme]);
|
}, [config.theme]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useDragSideBar() {
|
||||||
|
const limit = (x: number) => Math.min(500, Math.max(220, x));
|
||||||
|
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const startX = useRef(0);
|
||||||
|
const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300);
|
||||||
|
const lastUpdateTime = useRef(Date.now());
|
||||||
|
|
||||||
|
const handleMouseMove = useRef((e: MouseEvent) => {
|
||||||
|
if (Date.now() < lastUpdateTime.current + 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastUpdateTime.current = Date.now();
|
||||||
|
const d = e.clientX - startX.current;
|
||||||
|
const nextWidth = limit(startDragWidth.current + d);
|
||||||
|
chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth));
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMouseUp = useRef(() => {
|
||||||
|
startDragWidth.current = chatStore.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);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--sidebar-width",
|
||||||
|
`${limit(chatStore.config.sidebarWidth ?? 300)}px`,
|
||||||
|
);
|
||||||
|
}, [chatStore.config.sidebarWidth]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onDragMouseDown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const useHasHydrated = () => {
|
const useHasHydrated = () => {
|
||||||
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
|
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -101,6 +151,9 @@ function _Home() {
|
|||||||
const [openSettings, setOpenSettings] = useState(false);
|
const [openSettings, setOpenSettings] = useState(false);
|
||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
|
// drag side bar
|
||||||
|
const { onDragMouseDown } = useDragSideBar();
|
||||||
|
|
||||||
useSwitchTheme();
|
useSwitchTheme();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -174,6 +227,11 @@ function _Home() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={styles["sidebar-drag"]}
|
||||||
|
onMouseDown={(e) => onDragMouseDown(e as any)}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["window-content"]}>
|
<div className={styles["window-content"]}>
|
||||||
|
@ -19,11 +19,16 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-input {
|
.password-input-container {
|
||||||
|
max-width: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.password-eye {
|
.password-eye {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.password-input {
|
||||||
|
min-width: 80%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,13 +60,17 @@ function PasswordInput(props: HTMLProps<HTMLInputElement>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["password-input"]}>
|
<div className={styles["password-input-container"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
|
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
|
||||||
onClick={changeVisibility}
|
onClick={changeVisibility}
|
||||||
className={styles["password-eye"]}
|
className={styles["password-eye"]}
|
||||||
/>
|
/>
|
||||||
<input {...props} type={visible ? "text" : "password"} />
|
<input
|
||||||
|
{...props}
|
||||||
|
type={visible ? "text" : "password"}
|
||||||
|
className={styles["password-input"]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -120,8 +124,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
const builtinCount = SearchService.count.builtin;
|
const builtinCount = SearchService.count.builtin;
|
||||||
const customCount = promptStore.prompts.size ?? 0;
|
const customCount = promptStore.prompts.size ?? 0;
|
||||||
|
|
||||||
const showUsage = !!accessStore.token || !!accessStore.accessCode;
|
const showUsage = accessStore.isAuthorized();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkUpdate();
|
checkUpdate();
|
||||||
showUsage && checkUsage();
|
showUsage && checkUsage();
|
||||||
@ -342,37 +345,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
></input>
|
></input>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</List>
|
</List>
|
||||||
<List>
|
|
||||||
<SettingItem
|
|
||||||
title={Locale.Settings.Prompt.Disable.Title}
|
|
||||||
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={config.disablePromptHint}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig(
|
|
||||||
(config) =>
|
|
||||||
(config.disablePromptHint = e.currentTarget.checked),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
></input>
|
|
||||||
</SettingItem>
|
|
||||||
|
|
||||||
<SettingItem
|
|
||||||
title={Locale.Settings.Prompt.List}
|
|
||||||
subTitle={Locale.Settings.Prompt.ListCount(
|
|
||||||
builtinCount,
|
|
||||||
customCount,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
icon={<EditIcon />}
|
|
||||||
text={Locale.Settings.Prompt.Edit}
|
|
||||||
onClick={() => showToast(Locale.WIP)}
|
|
||||||
/>
|
|
||||||
</SettingItem>
|
|
||||||
</List>
|
|
||||||
<List>
|
<List>
|
||||||
{enabledAccessControl ? (
|
{enabledAccessControl ? (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
@ -469,6 +442,38 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<SettingItem
|
||||||
|
title={Locale.Settings.Prompt.Disable.Title}
|
||||||
|
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={config.disablePromptHint}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.disablePromptHint = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem
|
||||||
|
title={Locale.Settings.Prompt.List}
|
||||||
|
subTitle={Locale.Settings.Prompt.ListCount(
|
||||||
|
builtinCount,
|
||||||
|
customCount,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
icon={<EditIcon />}
|
||||||
|
text={Locale.Settings.Prompt.Edit}
|
||||||
|
onClick={() => showToast(Locale.WIP)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
</List>
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
<SettingItem title={Locale.Settings.Model}>
|
<SettingItem title={Locale.Settings.Model}>
|
||||||
<select
|
<select
|
||||||
|
@ -127,6 +127,7 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
.toast-content {
|
.toast-content {
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
@ -141,6 +142,7 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
.toast-action {
|
.toast-action {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
41
app/icons/max.svg
Normal file
41
app/icons/max.svg
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
|
||||||
|
height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<defs>
|
||||||
|
<rect id="path_0" x="0" y="0" width="16" height="16" />
|
||||||
|
</defs>
|
||||||
|
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
|
||||||
|
<mask id="bg-mask-0" fill="white">
|
||||||
|
<use xlink:href="#path_0"></use>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#bg-mask-0)">
|
||||||
|
<path id="路径 1"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"
|
||||||
|
d="M0,0L3.33,3.3 " />
|
||||||
|
<path id="路径 2"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"
|
||||||
|
d="M0,3.3L3.33,0 " />
|
||||||
|
<path id="路径 3"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"
|
||||||
|
d="M3.3,3.3L0,0 " />
|
||||||
|
<path id="路径 4"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"
|
||||||
|
d="M3.3,0L0,3.3 " />
|
||||||
|
<path id="路径 5"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(11 2) rotate(0 1.5 1.5)" d="M0,0L3,0L3,3 " />
|
||||||
|
<path id="路径 6"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(11 11) rotate(0 1.5 1.5)" d="M3,0L3,3L0,3 " />
|
||||||
|
<path id="路径 7"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 11) rotate(0 1.5 1.5)" d="M3,3L0,3L0,0 " />
|
||||||
|
<path id="路径 8"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 2) rotate(0 1.5 1.5)" d="M0,3L0,0L3,0 " />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
45
app/icons/min.svg
Normal file
45
app/icons/min.svg
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
|
||||||
|
height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<defs>
|
||||||
|
<rect id="path_0" x="0" y="0" width="16" height="16" />
|
||||||
|
</defs>
|
||||||
|
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
|
||||||
|
<mask id="bg-mask-0" fill="white">
|
||||||
|
<use xlink:href="#path_0"></use>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#bg-mask-0)">
|
||||||
|
<path id="路径 1"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"
|
||||||
|
d="M0,0L3.33,3.3 " />
|
||||||
|
<path id="路径 2"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"
|
||||||
|
d="M0,3.3L3.33,0 " />
|
||||||
|
<path id="路径 3"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"
|
||||||
|
d="M3.3,3.3L0,0 " />
|
||||||
|
<path id="路径 4"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"
|
||||||
|
d="M3.3,0L0,3.3 " />
|
||||||
|
<path id="路径 5"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.666666666666666 2.333333333333333) rotate(0 1.5 1.5)"
|
||||||
|
d="M0,0L0,3L3,3 " />
|
||||||
|
<path id="路径 6"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2.333333333333333 2.333333333333333) rotate(0 1.5 1.5)"
|
||||||
|
d="M3,0L3,3L0,3 " />
|
||||||
|
<path id="路径 7"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2.333333333333333 10.666666666666666) rotate(0 1.5 1.5)"
|
||||||
|
d="M3,3L3,0L0,0 " />
|
||||||
|
<path id="路径 8"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(10.666666666666666 10.666666666666666) rotate(0 1.4832500000000004 1.5)"
|
||||||
|
d="M0,3L0,0L2.97,0 " />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
17
app/icons/share.svg
Normal file
17
app/icons/share.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
|
||||||
|
height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<defs>
|
||||||
|
<rect id="path_0" x="0" y="0" width="16" height="16" />
|
||||||
|
</defs>
|
||||||
|
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
|
||||||
|
<mask id="bg-mask-0" fill="white">
|
||||||
|
<use xlink:href="#path_0"></use>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#bg-mask-0)">
|
||||||
|
<path id="路径 1"
|
||||||
|
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
|
||||||
|
transform="translate(2 1.3333333333333333) rotate(0 6.333333333333333 6.5)"
|
||||||
|
d="M6.67,3.67C1.67,3.67 0,7.33 0,13C0,13 2,8 6.67,8L6.67,11.67L12.67,6L6.67,0L6.67,3.67Z " />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 740 B |
@ -3,7 +3,7 @@ import { SubmitKey } from "../store/app";
|
|||||||
const cn = {
|
const cn = {
|
||||||
WIP: "该功能仍在开发中……",
|
WIP: "该功能仍在开发中……",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: "现在是未授权状态,请在设置页输入访问密码。",
|
Unauthorized: "现在是未授权状态,请点击左下角设置按钮输入访问密码。",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||||
@ -90,7 +90,7 @@ const cn = {
|
|||||||
},
|
},
|
||||||
SendKey: "发送键",
|
SendKey: "发送键",
|
||||||
Theme: "主题",
|
Theme: "主题",
|
||||||
TightBorder: "紧凑边框",
|
TightBorder: "无边框模式",
|
||||||
SendPreviewBubble: "发送预览气泡",
|
SendPreviewBubble: "发送预览气泡",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
|
@ -9,6 +9,7 @@ export interface AccessControlStore {
|
|||||||
updateToken: (_: string) => void;
|
updateToken: (_: string) => void;
|
||||||
updateCode: (_: string) => void;
|
updateCode: (_: string) => void;
|
||||||
enabledAccessControl: () => boolean;
|
enabledAccessControl: () => boolean;
|
||||||
|
isAuthorized: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ACCESS_KEY = "access-control";
|
export const ACCESS_KEY = "access-control";
|
||||||
@ -27,10 +28,13 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
updateToken(token: string) {
|
updateToken(token: string) {
|
||||||
set((state) => ({ token }));
|
set((state) => ({ token }));
|
||||||
},
|
},
|
||||||
|
isAuthorized() {
|
||||||
|
return !!get().token || !!get().accessCode;
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: ACCESS_KEY,
|
name: ACCESS_KEY,
|
||||||
version: 1,
|
version: 1,
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
@ -53,6 +53,7 @@ export interface ChatConfig {
|
|||||||
theme: Theme;
|
theme: Theme;
|
||||||
tightBorder: boolean;
|
tightBorder: boolean;
|
||||||
sendPreviewBubble: boolean;
|
sendPreviewBubble: boolean;
|
||||||
|
sidebarWidth: number;
|
||||||
|
|
||||||
disablePromptHint: boolean;
|
disablePromptHint: boolean;
|
||||||
|
|
||||||
@ -141,6 +142,7 @@ const DEFAULT_CONFIG: ChatConfig = {
|
|||||||
theme: Theme.Auto as Theme,
|
theme: Theme.Auto as Theme,
|
||||||
tightBorder: false,
|
tightBorder: false,
|
||||||
sendPreviewBubble: true,
|
sendPreviewBubble: true,
|
||||||
|
sidebarWidth: 300,
|
||||||
|
|
||||||
disablePromptHint: false,
|
disablePromptHint: false,
|
||||||
|
|
||||||
@ -205,7 +207,7 @@ interface ChatStore {
|
|||||||
moveSession: (from: number, to: number) => void;
|
moveSession: (from: number, to: number) => void;
|
||||||
selectSession: (index: number) => void;
|
selectSession: (index: number) => void;
|
||||||
newSession: () => void;
|
newSession: () => void;
|
||||||
deleteSession: () => void;
|
deleteSession: (index?: number) => void;
|
||||||
currentSession: () => ChatSession;
|
currentSession: () => ChatSession;
|
||||||
onNewMessage: (message: Message) => void;
|
onNewMessage: (message: Message) => void;
|
||||||
onUserInput: (content: string) => Promise<void>;
|
onUserInput: (content: string) => Promise<void>;
|
||||||
@ -326,24 +328,30 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSession() {
|
deleteSession(i?: number) {
|
||||||
const deletedSession = get().currentSession();
|
const deletedSession = get().currentSession();
|
||||||
const index = get().currentSessionIndex;
|
const index = i ?? get().currentSessionIndex;
|
||||||
const isLastSession = get().sessions.length === 1;
|
const isLastSession = get().sessions.length === 1;
|
||||||
if (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) {
|
if (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) {
|
||||||
get().removeSession(index);
|
get().removeSession(index);
|
||||||
|
|
||||||
showToast(Locale.Home.DeleteToast, {
|
showToast(
|
||||||
|
Locale.Home.DeleteToast,
|
||||||
|
{
|
||||||
text: Locale.Home.Revert,
|
text: Locale.Home.Revert,
|
||||||
onClick() {
|
onClick() {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
sessions: state.sessions
|
sessions: state.sessions
|
||||||
.slice(0, index)
|
.slice(0, index)
|
||||||
.concat([deletedSession])
|
.concat([deletedSession])
|
||||||
.concat(state.sessions.slice(index + Number(isLastSession))),
|
.concat(
|
||||||
|
state.sessions.slice(index + Number(isLastSession)),
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user