This commit is contained in:
GH Action - Upstream Sync 2023-06-26 01:24:47 +00:00
commit fb992cd4b9
17 changed files with 400 additions and 339 deletions

View File

@ -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=

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.
### `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

View File

@ -98,6 +98,10 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
如果你不想让用户使用 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.
### `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.

View File

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

View File

@ -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;
}
}

View File

@ -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 (
<div className={chatStyle["prompt-toast"]} key="prompt-toast">
<div className={styles["prompt-toast"]} key="prompt-toast">
{props.showToast && (
<div
className={chatStyle["prompt-toast-inner"] + " clickable"}
className={styles["prompt-toast-inner"] + " clickable"}
role="button"
onClick={() => props.setShowModal(true)}
>
<BrainIcon />
<span className={chatStyle["prompt-toast-content"]}>
<span className={styles["prompt-toast-content"]}>
{Locale.Context.Toast(context.length)}
</span>
</div>
@ -270,17 +271,15 @@ function ClearContextDivider() {
return (
<div
className={chatStyle["clear-context"]}
className={styles["clear-context"]}
onClick={() =>
chatStore.updateCurrentSession(
(session) => (session.clearContextIndex = undefined),
)
}
>
<div className={chatStyle["clear-context-tips"]}>
{Locale.Context.Clear}
</div>
<div className={chatStyle["clear-context-revert-btn"]}>
<div className={styles["clear-context-tips"]}>{Locale.Context.Clear}</div>
<div className={styles["clear-context-revert-btn"]}>
{Locale.Context.Revert}
</div>
</div>
@ -316,7 +315,7 @@ function ChatAction(props: {
return (
<div
className={`${chatStyle["chat-input-action"]} clickable`}
className={`${styles["chat-input-action"]} clickable`}
onClick={() => {
props.onClick();
setTimeout(updateWidth, 1);
@ -328,10 +327,10 @@ function ChatAction(props: {
} as React.CSSProperties
}
>
<div ref={iconRef} className={chatStyle["icon"]}>
<div ref={iconRef} className={styles["icon"]}>
{props.icon}
</div>
<div className={chatStyle["text"]} ref={textRef}>
<div className={styles["text"]} ref={textRef}>
{props.text}
</div>
</div>
@ -400,7 +399,7 @@ export function ChatActions(props: {
}
return (
<div className={chatStyle["chat-input-actions"]}>
<div className={styles["chat-input-actions"]}>
{couldStop && (
<ChatAction
onClick={stopAll}
@ -698,6 +697,24 @@ export function Chat() {
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
? []
: session.mask.context.slice();
@ -778,6 +795,19 @@ export function Chat() {
return (
<div className={styles.chat} key={session.id}>
<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-main-title " ${styles["chat-body-title"]}`}
@ -790,14 +820,7 @@ export function Chat() {
</div>
</div>
<div className="window-actions">
<div className={"window-action-button" + " " + styles.mobile}>
<IconButton
icon={<ReturnIcon />}
bordered
title={Locale.Chat.Actions.ChatList}
onClick={() => navigate(Path.Home)}
/>
</div>
{!isMobileScreen && (
<div className="window-action-button">
<IconButton
icon={<RenameIcon />}
@ -805,6 +828,7 @@ export function Chat() {
onClick={renameSession}
/>
</div>
)}
<div className="window-action-button">
<IconButton
icon={<ExportIcon />}
@ -880,40 +904,6 @@ export function Chat() {
</div>
)}
<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
content={message.content}
loading={
@ -929,9 +919,50 @@ export function Chat() {
parentRef={scrollRef}
defaultShow={i >= messages.length - 10}
/>
</div>
{!isUser && !message.preview && (
{showActions && (
<div className={styles["chat-message-actions"]}>
<div
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>
@ -939,6 +970,7 @@ export function Chat() {
)}
</div>
</div>
</div>
{shouldShowClearContextDivider && <ClearContextDivider />}
</>
);

View File

@ -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;

View File

@ -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);
}
}

View File

@ -522,6 +522,7 @@ export function Settings() {
</ListItem>
) : null}
{!accessStore.hideBalanceQuery ? (
<ListItem
title={Locale.Settings.Usage.Title}
subTitle={
@ -545,6 +546,7 @@ export function Settings() {
/>
)}
</ListItem>
) : null}
{!accessStore.hideUserApiKey ? (
<ListItem

View File

@ -12,6 +12,7 @@ declare global {
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
BUILD_MODE?: "standalone" | "export";
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,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
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} 条对话`,
},
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: {

View File

@ -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: {

View File

@ -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<AccessControlStore>()(
needCode: true,
hideUserApiKey: false,
openaiUrl: DEFAULT_OPENAI_URL,
hideBalanceQuery: false,
enabledAccessControl() {
get().fetch();

View File

@ -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;
}

View File

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