This commit is contained in:
GH Action - Upstream Sync 2023-06-26 00:46:35 +00:00
commit eb35735635
17 changed files with 400 additions and 339 deletions

View File

@ -27,3 +27,8 @@ HIDE_USER_API_KEY=
# Default: Empty # Default: Empty
# If you do not want users to use GPT-4, set this value to 1. # If you do not want users to use GPT-4, set this value to 1.
DISABLE_GPT4= DISABLE_GPT4=
# (optional)
# Default: Empty
# If you do not want users to query balance, set this value to 1.
HIDE_BALANCE_QUERY=

View File

@ -185,6 +185,12 @@ If you do not want users to input their own API key, set this value to 1.
If you do not want users to use GPT-4, set this value to 1. If you do not want users to use GPT-4, set this value to 1.
### `HIDE_BALANCE_QUERY` (optional)
> Default: Empty
If you do not want users to query balance, set this value to 1.
## Requirements ## Requirements
NodeJS >= 18, Docker >= 20 NodeJS >= 18, Docker >= 20

View File

@ -98,6 +98,10 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
如果你不想让用户使用 GPT-4将此环境变量设置为 1 即可。 如果你不想让用户使用 GPT-4将此环境变量设置为 1 即可。
### `HIDE_BALANCE_QUERY` (可选)
如果你不想让用户查询余额,将此环境变量设置为 1 即可。
## 开发 ## 开发
点击下方按钮,开始二次开发: 点击下方按钮,开始二次开发:

View File

@ -96,6 +96,10 @@ Si no desea que los usuarios rellenen la clave de API ellos mismos, establezca e
Si no desea que los usuarios utilicen GPT-4, establezca esta variable de entorno en 1. Si no desea que los usuarios utilicen GPT-4, establezca esta variable de entorno en 1.
### `HIDE_BALANCE_QUERY` (Opcional)
Si no desea que los usuarios consulte el saldo, establezca esta variable de entorno en 1.
## explotación ## explotación
> No se recomienda encarecidamente desarrollar o implementar localmente, debido a algunas razones técnicas, es difícil configurar el agente API de OpenAI localmente, a menos que pueda asegurarse de que puede conectarse directamente al servidor OpenAI. > No se recomienda encarecidamente desarrollar o implementar localmente, debido a algunas razones técnicas, es difícil configurar el agente API de OpenAI localmente, a menos que pueda asegurarse de que puede conectarse directamente al servidor OpenAI.

View File

@ -10,6 +10,7 @@ const DANGER_CONFIG = {
needCode: serverConfig.needCode, needCode: serverConfig.needCode,
hideUserApiKey: serverConfig.hideUserApiKey, hideUserApiKey: serverConfig.hideUserApiKey,
enableGPT4: serverConfig.enableGPT4, enableGPT4: serverConfig.enableGPT4,
hideBalanceQuery: serverConfig.hideBalanceQuery,
}; };
declare global { declare global {

View File

@ -15,7 +15,6 @@
animation: slide-in ease 0.3s; animation: slide-in ease 0.3s;
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
transition: all ease 0.3s; transition: all ease 0.3s;
margin-bottom: 10px;
align-items: center; align-items: center;
height: 16px; height: 16px;
width: var(--icon-width); width: var(--icon-width);
@ -202,3 +201,233 @@
} }
} }
} }
.chat {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.chat-body {
flex: 1;
overflow: auto;
padding: 20px;
padding-bottom: 40px;
position: relative;
overscroll-behavior: none;
}
.chat-body-title {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.chat-message {
display: flex;
flex-direction: row;
&:last-child {
animation: slide-in ease 0.3s;
}
}
.chat-message-user {
display: flex;
flex-direction: row-reverse;
}
.chat-message-container {
max-width: var(--message-max-width);
display: flex;
flex-direction: column;
align-items: flex-start;
}
.chat-message-user > .chat-message-container {
align-items: flex-end;
}
.chat-message-avatar {
margin-top: 20px;
}
.chat-message-status {
font-size: 12px;
color: #aaa;
line-height: 1.5;
margin-top: 5px;
}
.chat-message-item {
box-sizing: border-box;
max-width: 100%;
margin-top: 10px;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
font-size: 14px;
user-select: text;
word-break: break-word;
border: var(--border-in-light);
position: relative;
transition: all ease 0.3s;
min-width: 0;
&:hover {
min-width: 330px;
.chat-message-actions {
height: 40px;
opacity: 1;
transform: translateY(0px);
.chat-message-action-date {
opacity: 0.3;
}
}
}
.chat-message-actions {
display: flex;
width: 100%;
box-sizing: border-box;
font-size: 12px;
align-items: flex-end;
justify-content: space-between;
transition: all ease 0.3s;
transform: translateY(10px);
opacity: 0;
height: 0;
}
.chat-message-action-date {
color: var(--black);
opacity: 0;
}
}
.chat-message-user > .chat-message-container > .chat-message-item {
background-color: var(--second);
&:hover {
min-width: 0;
}
}
.chat-input-panel {
position: relative;
width: 100%;
padding: 20px;
padding-top: 10px;
box-sizing: border-box;
flex-direction: column;
border-top: var(--border-in-light);
box-shadow: var(--card-shadow);
.chat-input-actions {
.chat-input-action {
margin-bottom: 10px;
}
}
}
@mixin single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prompt-hints {
min-height: 20px;
width: 100%;
max-height: 50vh;
overflow: auto;
display: flex;
flex-direction: column-reverse;
background-color: var(--white);
border: var(--border-in-light);
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--shadow);
.prompt-hint {
color: var(--black);
padding: 6px 10px;
animation: slide-in ease 0.3s;
cursor: pointer;
transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;
&:not(:last-child) {
margin-top: 0;
}
.hint-title {
font-size: 12px;
font-weight: bolder;
@include single-line();
}
.hint-content {
font-size: 12px;
@include single-line();
}
&-selected,
&:hover {
border-color: var(--primary);
}
}
}
.chat-input-panel-inner {
display: flex;
flex: 1;
}
.chat-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px 90px 10px 14px;
resize: none;
outline: none;
box-sizing: border-box;
min-height: 68px;
}
.chat-input:focus {
border: 1px solid var(--primary);
}
.chat-input-send {
background-color: var(--primary);
color: white;
position: absolute;
right: 30px;
bottom: 32px;
}
@media only screen and (max-width: 600px) {
.chat-input {
font-size: 16px;
}
.chat-input-send {
bottom: 30px;
}
}

View File

@ -21,6 +21,8 @@ import MinIcon from "../icons/min.svg";
import ResetIcon from "../icons/reload.svg"; import ResetIcon from "../icons/reload.svg";
import BreakIcon from "../icons/break.svg"; import BreakIcon from "../icons/break.svg";
import SettingsIcon from "../icons/chat-settings.svg"; import SettingsIcon from "../icons/chat-settings.svg";
import DeleteIcon from "../icons/clear.svg";
import PinIcon from "../icons/pin.svg";
import LightIcon from "../icons/light.svg"; import LightIcon from "../icons/light.svg";
import DarkIcon from "../icons/dark.svg"; import DarkIcon from "../icons/dark.svg";
@ -57,10 +59,9 @@ import { Prompt, usePromptStore } from "../store/prompt";
import Locale from "../locales"; import Locale from "../locales";
import { IconButton } from "./button"; import { IconButton } from "./button";
import styles from "./home.module.scss"; import styles from "./chat.module.scss";
import chatStyle from "./chat.module.scss";
import { ListItem, Modal } from "./ui-lib"; import { ListItem, Modal, showToast } from "./ui-lib";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant"; import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji"; import { Avatar } from "./emoji";
@ -148,15 +149,15 @@ function PromptToast(props: {
const context = session.mask.context; const context = session.mask.context;
return ( return (
<div className={chatStyle["prompt-toast"]} key="prompt-toast"> <div className={styles["prompt-toast"]} key="prompt-toast">
{props.showToast && ( {props.showToast && (
<div <div
className={chatStyle["prompt-toast-inner"] + " clickable"} className={styles["prompt-toast-inner"] + " clickable"}
role="button" role="button"
onClick={() => props.setShowModal(true)} onClick={() => props.setShowModal(true)}
> >
<BrainIcon /> <BrainIcon />
<span className={chatStyle["prompt-toast-content"]}> <span className={styles["prompt-toast-content"]}>
{Locale.Context.Toast(context.length)} {Locale.Context.Toast(context.length)}
</span> </span>
</div> </div>
@ -270,17 +271,15 @@ function ClearContextDivider() {
return ( return (
<div <div
className={chatStyle["clear-context"]} className={styles["clear-context"]}
onClick={() => onClick={() =>
chatStore.updateCurrentSession( chatStore.updateCurrentSession(
(session) => (session.clearContextIndex = undefined), (session) => (session.clearContextIndex = undefined),
) )
} }
> >
<div className={chatStyle["clear-context-tips"]}> <div className={styles["clear-context-tips"]}>{Locale.Context.Clear}</div>
{Locale.Context.Clear} <div className={styles["clear-context-revert-btn"]}>
</div>
<div className={chatStyle["clear-context-revert-btn"]}>
{Locale.Context.Revert} {Locale.Context.Revert}
</div> </div>
</div> </div>
@ -316,7 +315,7 @@ function ChatAction(props: {
return ( return (
<div <div
className={`${chatStyle["chat-input-action"]} clickable`} className={`${styles["chat-input-action"]} clickable`}
onClick={() => { onClick={() => {
props.onClick(); props.onClick();
setTimeout(updateWidth, 1); setTimeout(updateWidth, 1);
@ -328,10 +327,10 @@ function ChatAction(props: {
} as React.CSSProperties } as React.CSSProperties
} }
> >
<div ref={iconRef} className={chatStyle["icon"]}> <div ref={iconRef} className={styles["icon"]}>
{props.icon} {props.icon}
</div> </div>
<div className={chatStyle["text"]} ref={textRef}> <div className={styles["text"]} ref={textRef}>
{props.text} {props.text}
</div> </div>
</div> </div>
@ -400,7 +399,7 @@ export function ChatActions(props: {
} }
return ( return (
<div className={chatStyle["chat-input-actions"]}> <div className={styles["chat-input-actions"]}>
{couldStop && ( {couldStop && (
<ChatAction <ChatAction
onClick={stopAll} onClick={stopAll}
@ -698,6 +697,24 @@ export function Chat() {
inputRef.current?.focus(); inputRef.current?.focus();
}; };
const onPinMessage = (botMessage: ChatMessage) => {
if (!botMessage.id) return;
const userMessageIndex = findLastUserIndex(botMessage.id);
if (userMessageIndex === null) return;
const userMessage = session.messages[userMessageIndex];
chatStore.updateCurrentSession((session) =>
session.mask.context.push(userMessage, botMessage),
);
showToast(Locale.Chat.Actions.PinToastContent, {
text: Locale.Chat.Actions.PinToastAction,
onClick: () => {
setShowPromptModal(true);
},
});
};
const context: RenderMessage[] = session.mask.hideContext const context: RenderMessage[] = session.mask.hideContext
? [] ? []
: session.mask.context.slice(); : session.mask.context.slice();
@ -778,6 +795,19 @@ export function Chat() {
return ( return (
<div className={styles.chat} key={session.id}> <div className={styles.chat} key={session.id}>
<div className="window-header" data-tauri-drag-region> <div className="window-header" data-tauri-drag-region>
{isMobileScreen && (
<div className="window-actions">
<div className={"window-action-button"}>
<IconButton
icon={<ReturnIcon />}
bordered
title={Locale.Chat.Actions.ChatList}
onClick={() => navigate(Path.Home)}
/>
</div>
</div>
)}
<div className="window-header-title"> <div className="window-header-title">
<div <div
className={`window-header-main-title " ${styles["chat-body-title"]}`} className={`window-header-main-title " ${styles["chat-body-title"]}`}
@ -790,21 +820,15 @@ export function Chat() {
</div> </div>
</div> </div>
<div className="window-actions"> <div className="window-actions">
<div className={"window-action-button" + " " + styles.mobile}> {!isMobileScreen && (
<IconButton <div className="window-action-button">
icon={<ReturnIcon />} <IconButton
bordered icon={<RenameIcon />}
title={Locale.Chat.Actions.ChatList} bordered
onClick={() => navigate(Path.Home)} onClick={renameSession}
/> />
</div> </div>
<div className="window-action-button"> )}
<IconButton
icon={<RenameIcon />}
bordered
onClick={renameSession}
/>
</div>
<div className="window-action-button"> <div className="window-action-button">
<IconButton <IconButton
icon={<ExportIcon />} icon={<ExportIcon />}
@ -880,40 +904,6 @@ export function Chat() {
</div> </div>
)} )}
<div className={styles["chat-message-item"]}> <div className={styles["chat-message-item"]}>
{showActions && (
<div className={styles["chat-message-top-actions"]}>
{message.streaming ? (
<div
className={styles["chat-message-top-action"]}
onClick={() => onUserStop(message.id ?? i)}
>
{Locale.Chat.Actions.Stop}
</div>
) : (
<>
<div
className={styles["chat-message-top-action"]}
onClick={() => onDelete(message.id ?? i)}
>
{Locale.Chat.Actions.Delete}
</div>
<div
className={styles["chat-message-top-action"]}
onClick={() => onResend(message.id ?? i)}
>
{Locale.Chat.Actions.Retry}
</div>
</>
)}
<div
className={styles["chat-message-top-action"]}
onClick={() => copyToClipboard(message.content)}
>
{Locale.Chat.Actions.Copy}
</div>
</div>
)}
<Markdown <Markdown
content={message.content} content={message.content}
loading={ loading={
@ -929,14 +919,56 @@ export function Chat() {
parentRef={scrollRef} parentRef={scrollRef}
defaultShow={i >= messages.length - 10} defaultShow={i >= messages.length - 10}
/> />
</div>
{!isUser && !message.preview && ( {showActions && (
<div className={styles["chat-message-actions"]}> <div className={styles["chat-message-actions"]}>
<div className={styles["chat-message-action-date"]}> <div
{message.date.toLocaleString()} className={styles["chat-input-actions"]}
style={{
marginTop: 10,
marginBottom: 0,
}}
>
{message.streaming ? (
<ChatAction
text={Locale.Chat.Actions.Stop}
icon={<StopIcon />}
onClick={() => onUserStop(message.id ?? i)}
/>
) : (
<>
<ChatAction
text={Locale.Chat.Actions.Delete}
icon={<DeleteIcon />}
onClick={() => onDelete(message.id ?? i)}
/>
<ChatAction
text={Locale.Chat.Actions.Retry}
icon={<ResetIcon />}
onClick={() => onResend(message.id ?? i)}
/>
<ChatAction
text={Locale.Chat.Actions.Pin}
icon={<PinIcon />}
onClick={() => onPinMessage(message)}
/>
</>
)}
<ChatAction
text={Locale.Chat.Actions.Copy}
icon={<CopyIcon />}
onClick={() => copyToClipboard(message.content)}
/>
</div>
<div className={styles["chat-message-action-date"]}>
{message.date.toLocaleString()}
</div>
</div> </div>
</div> )}
)} </div>
</div> </div>
</div> </div>
{shouldShowClearContextDivider && <ClearContextDivider />} {shouldShowClearContextDivider && <ClearContextDivider />}

View File

@ -313,243 +313,6 @@
margin-right: 15px; margin-right: 15px;
} }
.chat {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.chat-body {
flex: 1;
overflow: auto;
padding: 20px;
padding-bottom: 40px;
position: relative;
overscroll-behavior: none;
}
.chat-body-title {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.chat-message {
display: flex;
flex-direction: row;
&:last-child {
animation: slide-in ease 0.3s;
}
}
.chat-message-user {
display: flex;
flex-direction: row-reverse;
}
.chat-message-container {
max-width: var(--message-max-width);
display: flex;
flex-direction: column;
align-items: flex-start;
&:hover {
.chat-message-top-actions {
opacity: 1;
transform: translateX(10px);
pointer-events: all;
}
}
}
.chat-message-user > .chat-message-container {
align-items: flex-end;
}
.chat-message-avatar {
margin-top: 20px;
}
.chat-message-status {
font-size: 12px;
color: #aaa;
line-height: 1.5;
margin-top: 5px;
}
.chat-message-item {
box-sizing: border-box;
max-width: 100%;
margin-top: 10px;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
font-size: 14px;
user-select: text;
word-break: break-word;
border: var(--border-in-light);
position: relative;
}
.chat-message-top-actions {
min-width: 120px;
font-size: 12px;
position: absolute;
right: 20px;
top: -26px;
left: 30px;
transition: all ease 0.3s;
opacity: 0;
pointer-events: none;
display: flex;
flex-direction: row-reverse;
.chat-message-top-action {
opacity: 0.5;
color: var(--black);
white-space: nowrap;
cursor: pointer;
&:hover {
opacity: 1;
}
&:not(:first-child) {
margin-right: 10px;
}
}
}
.chat-message-user > .chat-message-container > .chat-message-item {
background-color: var(--second);
}
.chat-message-actions {
display: flex;
flex-direction: row-reverse;
width: 100%;
padding-top: 5px;
box-sizing: border-box;
font-size: 12px;
}
.chat-message-action-date {
color: #aaa;
}
.chat-input-panel {
position: relative;
width: 100%;
padding: 20px;
padding-top: 10px;
box-sizing: border-box;
flex-direction: column;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-top: var(--border-in-light);
box-shadow: var(--card-shadow);
}
@mixin single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prompt-hints {
min-height: 20px;
width: 100%;
max-height: 50vh;
overflow: auto;
display: flex;
flex-direction: column-reverse;
background-color: var(--white);
border: var(--border-in-light);
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--shadow);
.prompt-hint {
color: var(--black);
padding: 6px 10px;
animation: slide-in ease 0.3s;
cursor: pointer;
transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;
&:not(:last-child) {
margin-top: 0;
}
.hint-title {
font-size: 12px;
font-weight: bolder;
@include single-line();
}
.hint-content {
font-size: 12px;
@include single-line();
}
&-selected,
&:hover {
border-color: var(--primary);
}
}
}
.chat-input-panel-inner {
display: flex;
flex: 1;
}
.chat-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px 90px 10px 14px;
resize: none;
outline: none;
}
.chat-input:focus {
border: 1px solid var(--primary);
}
.chat-input-send {
background-color: var(--primary);
color: white;
position: absolute;
right: 30px;
bottom: 32px;
}
@media only screen and (max-width: 600px) {
.chat-input {
font-size: 16px;
}
.chat-input-send {
bottom: 30px;
}
}
.loading-content { .loading-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,12 +1,13 @@
.input-range { .input-range {
border: var(--border-in-light); border: var(--border-in-light);
border-radius: 10px; border-radius: 10px;
padding: 5px 15px 5px 10px; padding: 5px 10px 5px 10px;
font-size: 12px; font-size: 12px;
display: flex; display: flex;
justify-content: space-between;
max-width: 40%; max-width: 40%;
input[type="range"] { input[type="range"] {
max-width: calc(100% - 50px); max-width: calc(100% - 34px);
} }
} }

View File

@ -522,29 +522,31 @@ export function Settings() {
</ListItem> </ListItem>
) : null} ) : null}
<ListItem {!accessStore.hideBalanceQuery ? (
title={Locale.Settings.Usage.Title} <ListItem
subTitle={ title={Locale.Settings.Usage.Title}
showUsage subTitle={
? loadingUsage showUsage
? Locale.Settings.Usage.IsChecking ? loadingUsage
: Locale.Settings.Usage.SubTitle( ? Locale.Settings.Usage.IsChecking
usage?.used ?? "[?]", : Locale.Settings.Usage.SubTitle(
usage?.subscription ?? "[?]", usage?.used ?? "[?]",
) usage?.subscription ?? "[?]",
: Locale.Settings.Usage.NoAccess )
} : Locale.Settings.Usage.NoAccess
> }
{!showUsage || loadingUsage ? ( >
<div /> {!showUsage || loadingUsage ? (
) : ( <div />
<IconButton ) : (
icon={<ResetIcon></ResetIcon>} <IconButton
text={Locale.Settings.Usage.Check} icon={<ResetIcon></ResetIcon>}
onClick={() => checkUsage(true)} text={Locale.Settings.Usage.Check}
/> onClick={() => checkUsage(true)}
)} />
</ListItem> )}
</ListItem>
) : null}
{!accessStore.hideUserApiKey ? ( {!accessStore.hideUserApiKey ? (
<ListItem <ListItem

View File

@ -12,6 +12,7 @@ declare global {
DISABLE_GPT4?: string; // allow user to use gpt-4 or not DISABLE_GPT4?: string; // allow user to use gpt-4 or not
BUILD_MODE?: "standalone" | "export"; BUILD_MODE?: "standalone" | "export";
BUILD_APP?: string; // is building desktop app BUILD_APP?: string; // is building desktop app
HIDE_BALANCE_QUERY?: string; // allow user to query balance or not
} }
} }
} }
@ -46,5 +47,6 @@ export const getServerSideConfig = () => {
isVercel: !!process.env.VERCEL, isVercel: !!process.env.VERCEL,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY, hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
enableGPT4: !process.env.DISABLE_GPT4, enableGPT4: !process.env.DISABLE_GPT4,
hideBalanceQuery: !!process.env.HIDE_BALANCE_QUERY,
}; };
}; };

1
app/icons/pin.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -17,7 +17,7 @@ const cn = {
ChatItemCount: (count: number) => `${count} 条对话`, ChatItemCount: (count: number) => `${count} 条对话`,
}, },
Chat: { Chat: {
SubTitle: (count: number) => `与 ChatGPT 的 ${count} 条对话`, SubTitle: (count: number) => ` ${count} 条对话`,
Actions: { Actions: {
ChatList: "查看消息列表", ChatList: "查看消息列表",
CompressedHistory: "查看压缩后的历史 Prompt", CompressedHistory: "查看压缩后的历史 Prompt",
@ -25,6 +25,9 @@ const cn = {
Copy: "复制", Copy: "复制",
Stop: "停止", Stop: "停止",
Retry: "重试", Retry: "重试",
Pin: "固定",
PinToastContent: "已将 2 条对话固定至预设提示词",
PinToastAction: "查看",
Delete: "删除", Delete: "删除",
}, },
Commands: { Commands: {

View File

@ -18,7 +18,7 @@ const en: LocaleType = {
ChatItemCount: (count: number) => `${count} messages`, ChatItemCount: (count: number) => `${count} messages`,
}, },
Chat: { Chat: {
SubTitle: (count: number) => `${count} messages with ChatGPT`, SubTitle: (count: number) => `${count} messages`,
Actions: { Actions: {
ChatList: "Go To Chat List", ChatList: "Go To Chat List",
CompressedHistory: "Compressed History Memory Prompt", CompressedHistory: "Compressed History Memory Prompt",
@ -26,6 +26,9 @@ const en: LocaleType = {
Copy: "Copy", Copy: "Copy",
Stop: "Stop", Stop: "Stop",
Retry: "Retry", Retry: "Retry",
Pin: "Pin",
PinToastContent: "Pinned 2 messages to contextual prompts",
PinToastAction: "View",
Delete: "Delete", Delete: "Delete",
}, },
Commands: { Commands: {

View File

@ -13,6 +13,7 @@ export interface AccessControlStore {
needCode: boolean; needCode: boolean;
hideUserApiKey: boolean; hideUserApiKey: boolean;
openaiUrl: string; openaiUrl: string;
hideBalanceQuery: boolean;
updateToken: (_: string) => void; updateToken: (_: string) => void;
updateCode: (_: string) => void; updateCode: (_: string) => void;
@ -36,6 +37,7 @@ export const useAccessStore = create<AccessControlStore>()(
needCode: true, needCode: true,
hideUserApiKey: false, hideUserApiKey: false,
openaiUrl: DEFAULT_OPENAI_URL, openaiUrl: DEFAULT_OPENAI_URL,
hideBalanceQuery: false,
enabledAccessControl() { enabledAccessControl() {
get().fetch(); get().fetch();

View File

@ -24,7 +24,10 @@
.window-header-sub-title { .window-header-sub-title {
font-size: 14px; font-size: 14px;
margin-top: 5px; }
@media screen and (max-width: 600px) {
text-align: center;
} }
} }
@ -32,6 +35,6 @@
display: inline-flex; display: inline-flex;
} }
.window-action-button { .window-action-button:not(:first-child) {
margin-left: 10px; margin-left: 10px;
} }

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "chatgpt-next-web", "productName": "chatgpt-next-web",
"version": "2.8.3" "version": "2.8.4"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {