diff --git a/.env.template b/.env.template
index 5f3fc02da..0f4bf0e7c 100644
--- a/.env.template
+++ b/.env.template
@@ -27,3 +27,8 @@ HIDE_USER_API_KEY=
# Default: Empty
# If you do not want users to use GPT-4, set this value to 1.
DISABLE_GPT4=
+
+# (optional)
+# Default: Empty
+# If you do not want users to query balance, set this value to 1.
+HIDE_BALANCE_QUERY=
\ No newline at end of file
diff --git a/README.md b/README.md
index 148c137f8..91c857f1f 100644
--- a/README.md
+++ b/README.md
@@ -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.
+### `HIDE_BALANCE_QUERY` (optional)
+
+> Default: Empty
+
+If you do not want users to query balance, set this value to 1.
+
## Requirements
NodeJS >= 18, Docker >= 20
diff --git a/README_CN.md b/README_CN.md
index f1be5cc98..89a6f39f0 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -98,6 +98,10 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
如果你不想让用户使用 GPT-4,将此环境变量设置为 1 即可。
+### `HIDE_BALANCE_QUERY` (可选)
+
+如果你不想让用户查询余额,将此环境变量设置为 1 即可。
+
## 开发
点击下方按钮,开始二次开发:
diff --git a/README_ES.md b/README_ES.md
index cdd835908..e9705e402 100644
--- a/README_ES.md
+++ b/README_ES.md
@@ -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.
+### `HIDE_BALANCE_QUERY` (Opcional)
+
+Si no desea que los usuarios consulte el saldo, establezca esta variable de entorno en 1.
+
## 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.
diff --git a/app/api/config/route.ts b/app/api/config/route.ts
index 2b3bcbf20..6b9565588 100644
--- a/app/api/config/route.ts
+++ b/app/api/config/route.ts
@@ -10,6 +10,7 @@ const DANGER_CONFIG = {
needCode: serverConfig.needCode,
hideUserApiKey: serverConfig.hideUserApiKey,
enableGPT4: serverConfig.enableGPT4,
+ hideBalanceQuery: serverConfig.hideBalanceQuery,
};
declare global {
diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss
index 644c917a1..839e1ff58 100644
--- a/app/components/chat.module.scss
+++ b/app/components/chat.module.scss
@@ -15,7 +15,6 @@
animation: slide-in ease 0.3s;
box-shadow: var(--card-shadow);
transition: all ease 0.3s;
- margin-bottom: 10px;
align-items: center;
height: 16px;
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;
+ }
+}
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index e1011e422..b36b47630 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -21,6 +21,8 @@ import MinIcon from "../icons/min.svg";
import ResetIcon from "../icons/reload.svg";
import BreakIcon from "../icons/break.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 DarkIcon from "../icons/dark.svg";
@@ -57,10 +59,9 @@ import { Prompt, usePromptStore } from "../store/prompt";
import Locale from "../locales";
import { IconButton } from "./button";
-import styles from "./home.module.scss";
-import chatStyle from "./chat.module.scss";
+import styles 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 { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji";
@@ -148,15 +149,15 @@ function PromptToast(props: {
const context = session.mask.context;
return (
-
+
{props.showToast && (
props.setShowModal(true)}
>
-
+
{Locale.Context.Toast(context.length)}
@@ -270,17 +271,15 @@ function ClearContextDivider() {
return (
chatStore.updateCurrentSession(
(session) => (session.clearContextIndex = undefined),
)
}
>
-
- {Locale.Context.Clear}
-
-
+
{Locale.Context.Clear}
+
{Locale.Context.Revert}
@@ -316,7 +315,7 @@ function ChatAction(props: {
return (
{
props.onClick();
setTimeout(updateWidth, 1);
@@ -328,10 +327,10 @@ function ChatAction(props: {
} as React.CSSProperties
}
>
-
+
{props.icon}
-
@@ -400,7 +399,7 @@ export function ChatActions(props: {
}
return (
-
+
{couldStop && (
{
+ 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
? []
: session.mask.context.slice();
@@ -778,6 +795,19 @@ export function Chat() {
return (
+ {isMobileScreen && (
+
+
+ }
+ bordered
+ title={Locale.Chat.Actions.ChatList}
+ onClick={() => navigate(Path.Home)}
+ />
+
+
+ )}
+
-
- }
- bordered
- title={Locale.Chat.Actions.ChatList}
- onClick={() => navigate(Path.Home)}
- />
-
-
- }
- bordered
- onClick={renameSession}
- />
-
+ {!isMobileScreen && (
+
+ }
+ bordered
+ onClick={renameSession}
+ />
+
+ )}
}
@@ -880,40 +904,6 @@ export function Chat() {
)}
- {showActions && (
-
- {message.streaming ? (
-
onUserStop(message.id ?? i)}
- >
- {Locale.Chat.Actions.Stop}
-
- ) : (
- <>
-
onDelete(message.id ?? i)}
- >
- {Locale.Chat.Actions.Delete}
-
-
onResend(message.id ?? i)}
- >
- {Locale.Chat.Actions.Retry}
-
- >
- )}
-
-
copyToClipboard(message.content)}
- >
- {Locale.Chat.Actions.Copy}
-
-
- )}
= messages.length - 10}
/>
-
- {!isUser && !message.preview && (
-
-
- {message.date.toLocaleString()}
+
+ {showActions && (
+
+
+ {message.streaming ? (
+ }
+ onClick={() => onUserStop(message.id ?? i)}
+ />
+ ) : (
+ <>
+ }
+ onClick={() => onDelete(message.id ?? i)}
+ />
+
+ }
+ onClick={() => onResend(message.id ?? i)}
+ />
+
+ }
+ onClick={() => onPinMessage(message)}
+ />
+ >
+ )}
+ }
+ onClick={() => copyToClipboard(message.content)}
+ />
+
+
+
+ {message.date.toLocaleString()}
+
-
- )}
+ )}
+
{shouldShowClearContextDivider &&
}
diff --git a/app/components/home.module.scss b/app/components/home.module.scss
index 81916d71b..49ad2bd22 100644
--- a/app/components/home.module.scss
+++ b/app/components/home.module.scss
@@ -313,243 +313,6 @@
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 {
display: flex;
flex-direction: column;
diff --git a/app/components/input-range.module.scss b/app/components/input-range.module.scss
index e97410529..ec7d4118b 100644
--- a/app/components/input-range.module.scss
+++ b/app/components/input-range.module.scss
@@ -1,12 +1,13 @@
.input-range {
border: var(--border-in-light);
border-radius: 10px;
- padding: 5px 15px 5px 10px;
+ padding: 5px 10px 5px 10px;
font-size: 12px;
display: flex;
+ justify-content: space-between;
max-width: 40%;
input[type="range"] {
- max-width: calc(100% - 50px);
+ max-width: calc(100% - 34px);
}
}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 9e377478f..41fed620c 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -522,29 +522,31 @@ export function Settings() {
) : null}
-
- {!showUsage || loadingUsage ? (
-
- ) : (
- }
- text={Locale.Settings.Usage.Check}
- onClick={() => checkUsage(true)}
- />
- )}
-
+ {!accessStore.hideBalanceQuery ? (
+
+ {!showUsage || loadingUsage ? (
+
+ ) : (
+ }
+ text={Locale.Settings.Usage.Check}
+ onClick={() => checkUsage(true)}
+ />
+ )}
+
+ ) : null}
{!accessStore.hideUserApiKey ? (
{
isVercel: !!process.env.VERCEL,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
enableGPT4: !process.env.DISABLE_GPT4,
+ hideBalanceQuery: !!process.env.HIDE_BALANCE_QUERY,
};
};
diff --git a/app/icons/pin.svg b/app/icons/pin.svg
new file mode 100644
index 000000000..caf7b0ee4
--- /dev/null
+++ b/app/icons/pin.svg
@@ -0,0 +1 @@
+
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 14ee7ec99..ae22efa9f 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -17,7 +17,7 @@ const cn = {
ChatItemCount: (count: number) => `${count} 条对话`,
},
Chat: {
- SubTitle: (count: number) => `与 ChatGPT 的 ${count} 条对话`,
+ SubTitle: (count: number) => `共 ${count} 条对话`,
Actions: {
ChatList: "查看消息列表",
CompressedHistory: "查看压缩后的历史 Prompt",
@@ -25,6 +25,9 @@ const cn = {
Copy: "复制",
Stop: "停止",
Retry: "重试",
+ Pin: "固定",
+ PinToastContent: "已将 2 条对话固定至预设提示词",
+ PinToastAction: "查看",
Delete: "删除",
},
Commands: {
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 1659ddb1f..5261e7ede 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -18,7 +18,7 @@ const en: LocaleType = {
ChatItemCount: (count: number) => `${count} messages`,
},
Chat: {
- SubTitle: (count: number) => `${count} messages with ChatGPT`,
+ SubTitle: (count: number) => `${count} messages`,
Actions: {
ChatList: "Go To Chat List",
CompressedHistory: "Compressed History Memory Prompt",
@@ -26,6 +26,9 @@ const en: LocaleType = {
Copy: "Copy",
Stop: "Stop",
Retry: "Retry",
+ Pin: "Pin",
+ PinToastContent: "Pinned 2 messages to contextual prompts",
+ PinToastAction: "View",
Delete: "Delete",
},
Commands: {
diff --git a/app/store/access.ts b/app/store/access.ts
index 0601903d3..e9d09bb84 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -13,6 +13,7 @@ export interface AccessControlStore {
needCode: boolean;
hideUserApiKey: boolean;
openaiUrl: string;
+ hideBalanceQuery: boolean;
updateToken: (_: string) => void;
updateCode: (_: string) => void;
@@ -36,6 +37,7 @@ export const useAccessStore = create()(
needCode: true,
hideUserApiKey: false,
openaiUrl: DEFAULT_OPENAI_URL,
+ hideBalanceQuery: false,
enabledAccessControl() {
get().fetch();
diff --git a/app/styles/window.scss b/app/styles/window.scss
index a92aed4eb..2caae8fb3 100644
--- a/app/styles/window.scss
+++ b/app/styles/window.scss
@@ -24,7 +24,10 @@
.window-header-sub-title {
font-size: 14px;
- margin-top: 5px;
+ }
+
+ @media screen and (max-width: 600px) {
+ text-align: center;
}
}
@@ -32,6 +35,6 @@
display: inline-flex;
}
-.window-action-button {
+.window-action-button:not(:first-child) {
margin-left: 10px;
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 967ad2cd1..2a8be4908 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -8,7 +8,7 @@
},
"package": {
"productName": "chatgpt-next-web",
- "version": "2.8.3"
+ "version": "2.8.4"
},
"tauri": {
"allowlist": {