mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-09 19:46:37 +08:00
Merge remote-tracking branch 'upstream/main' into dev
This commit is contained in:
commit
5bcec0814d
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
@ -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,19 @@ export function Chat(props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{!isMobileScreen() && (
|
||||||
|
<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,53 @@ 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(() => {
|
||||||
|
if (isMobileScreen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +155,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) {
|
||||||
@ -172,6 +229,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} 条对话`,
|
||||||
@ -72,6 +72,7 @@ const cn = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "头像",
|
Avatar: "头像",
|
||||||
@ -90,7 +91,7 @@ const cn = {
|
|||||||
},
|
},
|
||||||
SendKey: "发送键",
|
SendKey: "发送键",
|
||||||
Theme: "主题",
|
Theme: "主题",
|
||||||
TightBorder: "紧凑边框",
|
TightBorder: "无边框模式",
|
||||||
SendPreviewBubble: "发送预览气泡",
|
SendPreviewBubble: "发送预览气泡",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
|
@ -75,6 +75,7 @@ const en: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@ -75,6 +75,7 @@ const es: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@ -3,10 +3,11 @@ import EN from "./en";
|
|||||||
import TW from "./tw";
|
import TW from "./tw";
|
||||||
import ES from "./es";
|
import ES from "./es";
|
||||||
import IT from "./it";
|
import IT from "./it";
|
||||||
|
import TR from "./tr";
|
||||||
|
|
||||||
export type { LocaleType } from "./cn";
|
export type { LocaleType } from "./cn";
|
||||||
|
|
||||||
export const AllLangs = ["en", "cn", "tw", "es", "it"] as const;
|
export const AllLangs = ["en", "cn", "tw", "es", "it", "tr"] as const;
|
||||||
type Lang = (typeof AllLangs)[number];
|
type Lang = (typeof AllLangs)[number];
|
||||||
|
|
||||||
const LANG_KEY = "lang";
|
const LANG_KEY = "lang";
|
||||||
@ -50,6 +51,8 @@ export function getLang(): Lang {
|
|||||||
return "es";
|
return "es";
|
||||||
} else if (lang.includes("it")) {
|
} else if (lang.includes("it")) {
|
||||||
return "it";
|
return "it";
|
||||||
|
} else if (lang.includes("tr")) {
|
||||||
|
return "tr";
|
||||||
} else {
|
} else {
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
@ -60,4 +63,4 @@ export function changeLang(lang: Lang) {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT }[getLang()];
|
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT, tr: TR }[getLang()];
|
||||||
|
@ -75,6 +75,7 @@ const it: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
177
app/locales/tr.ts
Normal file
177
app/locales/tr.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import { SubmitKey } from "../store/app";
|
||||||
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
|
const tr: LocaleType = {
|
||||||
|
WIP: "Çalışma devam ediyor...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized:
|
||||||
|
"Yetkisiz erişim, lütfen erişim kodunu ayarlar sayfasından giriniz.",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} mesaj`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `ChatGPT tarafından ${count} mesaj`,
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Sohbet Listesine Git",
|
||||||
|
CompressedHistory: "Sıkıştırılmış Geçmiş Bellek Komutu",
|
||||||
|
Export: "Tüm Mesajları Markdown Olarak Dışa Aktar",
|
||||||
|
Copy: "Kopyala",
|
||||||
|
Stop: "Durdur",
|
||||||
|
Retry: "Tekrar Dene",
|
||||||
|
},
|
||||||
|
Rename: "Sohbeti Yeniden Adlandır",
|
||||||
|
Typing: "Yazıyor…",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `Göndermek için ${submitKey}`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", kaydırmak için Shift + Enter";
|
||||||
|
}
|
||||||
|
return inputHints + ", komutları aramak için / (eğik çizgi)";
|
||||||
|
},
|
||||||
|
Send: "Gönder",
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Tüm Mesajlar",
|
||||||
|
Copy: "Tümünü Kopyala",
|
||||||
|
Download: "İndir",
|
||||||
|
MessageFromYou: "Sizin Mesajınız",
|
||||||
|
MessageFromChatGPT: "ChatGPT'nin Mesajı",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Bellek Komutları",
|
||||||
|
EmptyContent: "Henüz değil.",
|
||||||
|
Send: "Belleği Gönder",
|
||||||
|
Copy: "Belleği Kopyala",
|
||||||
|
Reset: "Oturumu Sıfırla",
|
||||||
|
ResetConfirm:
|
||||||
|
"Sıfırlama, geçerli görüşme geçmişini ve geçmiş belleği siler. Sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Yeni Sohbet",
|
||||||
|
DeleteChat: "Seçili sohbeti silmeyi onaylıyor musunuz?",
|
||||||
|
DeleteToast: "Sohbet Silindi",
|
||||||
|
Revert: "Geri Al",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Ayarlar",
|
||||||
|
SubTitle: "Tüm Ayarlar",
|
||||||
|
Actions: {
|
||||||
|
ClearAll: "Tüm Verileri Temizle",
|
||||||
|
ResetAll: "Tüm Ayarları Sıfırla",
|
||||||
|
Close: "Kapat",
|
||||||
|
ConfirmResetAll: {
|
||||||
|
Confirm: "Tüm ayarları sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
ConfirmClearAll: {
|
||||||
|
Confirm: "Tüm sohbeti sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
|
Options: {
|
||||||
|
cn: "简体中文",
|
||||||
|
en: "English",
|
||||||
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Avatar: "Avatar",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Yazı Boyutu",
|
||||||
|
SubTitle: "Sohbet içeriğinin yazı boyutunu ayarlayın",
|
||||||
|
},
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Sürüm: ${x}`,
|
||||||
|
IsLatest: "En son sürüm",
|
||||||
|
CheckUpdate: "Güncellemeyi Kontrol Et",
|
||||||
|
IsChecking: "Güncelleme kontrol ediliyor...",
|
||||||
|
FoundUpdate: (x: string) => `Yeni sürüm bulundu: ${x}`,
|
||||||
|
GoToUpdate: "Güncelle",
|
||||||
|
},
|
||||||
|
SendKey: "Gönder Tuşu",
|
||||||
|
Theme: "Tema",
|
||||||
|
TightBorder: "Tam Ekran",
|
||||||
|
SendPreviewBubble: "Mesaj Önizleme Balonu",
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Otomatik tamamlamayı devre dışı bırak",
|
||||||
|
SubTitle: "Otomatik tamamlamayı kullanmak için / (eğik çizgi) girin",
|
||||||
|
},
|
||||||
|
List: "Komut Listesi",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
||||||
|
Edit: "Düzenle",
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Ekli Mesaj Sayısı",
|
||||||
|
SubTitle: "İstek başına ekli gönderilen mesaj sayısı",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Geçmiş Sıkıştırma Eşiği",
|
||||||
|
SubTitle:
|
||||||
|
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
|
||||||
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API Anahtarı",
|
||||||
|
SubTitle: "Erişim kodu sınırını yoksaymak için anahtarınızı kullanın",
|
||||||
|
Placeholder: "OpenAI API Anahtarı",
|
||||||
|
},
|
||||||
|
Usage: {
|
||||||
|
Title: "Hesap Bakiyesi",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Bu ay kullanılan $${used}, abonelik $${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Kontrol ediliyor...",
|
||||||
|
Check: "Tekrar Kontrol Et",
|
||||||
|
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
|
||||||
|
},
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Erişim Kodu",
|
||||||
|
SubTitle: "Erişim kontrolü etkinleştirme",
|
||||||
|
Placeholder: "Erişim Kodu Gerekiyor",
|
||||||
|
},
|
||||||
|
Model: "Model",
|
||||||
|
Temperature: {
|
||||||
|
Title: "Gerçeklik",
|
||||||
|
SubTitle: "Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Maksimum Belirteç",
|
||||||
|
SubTitle: "Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||||
|
},
|
||||||
|
PresencePenlty: {
|
||||||
|
Title: "Varlık Cezası",
|
||||||
|
SubTitle:
|
||||||
|
"Daha büyük bir değer, yeni konular hakkında konuşma olasılığını artırır",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Yeni Konuşma",
|
||||||
|
BotHello: "Merhaba! Size bugün nasıl yardımcı olabilirim?",
|
||||||
|
Error: "Bir şeyler yanlış gitti. Lütfen daha sonra tekrar deneyiniz.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Bu, yapay zeka ile kullanıcı arasındaki sohbet geçmişinin bir özetidir: " +
|
||||||
|
content,
|
||||||
|
Topic:
|
||||||
|
"Lütfen herhangi bir giriş, noktalama işareti, tırnak işareti, nokta, sembol veya ek metin olmadan konuşmamızı özetleyen dört ila beş kelimelik bir başlık oluşturun. Çevreleyen tırnak işaretlerini kaldırın.",
|
||||||
|
Summarize:
|
||||||
|
"Gelecekteki bağlam için bir bilgi istemi olarak kullanmak üzere tartışmamızı en fazla 200 kelimeyle özetleyin.",
|
||||||
|
},
|
||||||
|
ConfirmClearAll: "Tüm sohbet ve ayar verilerini temizlemeyi onaylıyor musunuz?",
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Panoya kopyalandı",
|
||||||
|
Failed: "Kopyalama başarısız oldu, lütfen panoya erişim izni verin",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `${x} bağlamsal bellek komutu`,
|
||||||
|
Edit: "Bağlamsal ve Bellek Komutları",
|
||||||
|
Add: "Yeni Ekle",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tr;
|
@ -73,6 +73,7 @@ const tw: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "大頭貼",
|
Avatar: "大頭貼",
|
||||||
|
@ -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,14 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
updateToken(token: string) {
|
updateToken(token: string) {
|
||||||
set((state) => ({ token }));
|
set((state) => ({ token }));
|
||||||
},
|
},
|
||||||
|
isAuthorized() {
|
||||||
|
// has token or has code or disabled access control
|
||||||
|
return !!get().token || !!get().accessCode || !get().enabledAccessControl();
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
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(
|
||||||
text: Locale.Home.Revert,
|
Locale.Home.DeleteToast,
|
||||||
onClick() {
|
{
|
||||||
set((state) => ({
|
text: Locale.Home.Revert,
|
||||||
sessions: state.sessions
|
onClick() {
|
||||||
.slice(0, index)
|
set((state) => ({
|
||||||
.concat([deletedSession])
|
sessions: state.sessions
|
||||||
.concat(state.sessions.slice(index + Number(isLastSession))),
|
.slice(0, index)
|
||||||
}));
|
.concat([deletedSession])
|
||||||
|
.concat(
|
||||||
|
state.sessions.slice(index + Number(isLastSession)),
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
5000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user