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}
-
+
{props.text}
@@ -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": {