forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'Yidadaa:main' into main
This commit is contained in:
commit
502d22bd20
@ -35,14 +35,17 @@ One-Click to deploy your own ChatGPT web UI.
|
|||||||
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- Automatically compresses chat history to support long conversations while also saving your tokens
|
- Automatically compresses chat history to support long conversations while also saving your tokens
|
||||||
- One-click export all chat history with full Markdown support
|
- One-click export all chat history with full Markdown support
|
||||||
|
- I18n supported
|
||||||
|
|
||||||
## 开发计划 Roadmap
|
## 开发计划 Roadmap
|
||||||
|
|
||||||
- System Prompt: pin a user defined prompt as system prompt 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
- System Prompt: pin a user defined prompt as system prompt 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||||
- User Prompt: user can edit and save custom prompts to prompt list 允许用户自行编辑内置 Prompt 列表
|
- User Prompt: user can edit and save custom prompts to prompt list 允许用户自行编辑内置 Prompt 列表
|
||||||
- Self-host Model: support llama, alpaca, ChatGLM, BELLE etc. 支持自部署的大语言模型
|
- Self-host Model: support llama, alpaca, ChatGLM, BELLE etc. 支持自部署的大语言模型
|
||||||
- Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
- Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
|
|
||||||
### 不会开发的功能 Not in Plan
|
### 不会开发的功能 Not in Plan
|
||||||
|
|
||||||
- User login, accounts, cloud sync 用户登录、账号管理、消息云同步
|
- User login, accounts, cloud sync 用户登录、账号管理、消息云同步
|
||||||
- UI text customize 界面文字自定义
|
- UI text customize 界面文字自定义
|
||||||
|
|
||||||
@ -179,9 +182,10 @@ docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-
|
|||||||
|
|
||||||
![更多展示 More](./static/more.png)
|
![更多展示 More](./static/more.png)
|
||||||
|
|
||||||
|
|
||||||
## 捐赠 Donate USDT
|
## 捐赠 Donate USDT
|
||||||
|
|
||||||
> BNB Smart Chain (BEP 20)
|
> BNB Smart Chain (BEP 20)
|
||||||
|
|
||||||
```
|
```
|
||||||
0x67cD02c7EB62641De576a1fA3EdB32eA0c3ffD89
|
0x67cD02c7EB62641De576a1fA3EdB32eA0c3ffD89
|
||||||
```
|
```
|
||||||
|
@ -8,6 +8,7 @@ export function IconButton(props: {
|
|||||||
text?: string;
|
text?: string;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
shadow?: boolean;
|
shadow?: boolean;
|
||||||
|
noDark?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
@ -23,7 +24,11 @@ export function IconButton(props: {
|
|||||||
title={props.title}
|
title={props.title}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div className={styles["icon-button-icon"]}>{props.icon}</div>
|
<div
|
||||||
|
className={styles["icon-button-icon"] + ` ${props.noDark && "no-dark"}`}
|
||||||
|
>
|
||||||
|
{props.icon}
|
||||||
|
</div>
|
||||||
{props.text && (
|
{props.text && (
|
||||||
<div className={styles["icon-button-text"]}>{props.text}</div>
|
<div className={styles["icon-button-text"]}>{props.text}</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
.prompt-toast {
|
.prompt-toast {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -50px;
|
bottom: -50px;
|
||||||
@ -19,6 +21,8 @@
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
|
|
||||||
|
animation: slide-in-from-top ease 0.3s;
|
||||||
|
|
||||||
.prompt-toast-content {
|
.prompt-toast-content {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
|
|||||||
export function Avatar(props: { role: Message["role"] }) {
|
export function Avatar(props: { role: Message["role"] }) {
|
||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
if (props.role === "assistant") {
|
if (props.role !== "user") {
|
||||||
return <BotIcon className={styles["user-avtar"]} />;
|
return <BotIcon className={styles["user-avtar"]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,8 @@ function exportMessages(messages: Message[], topic: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PromptToast(props: {
|
function PromptToast(props: {
|
||||||
showModal: boolean;
|
showToast?: boolean;
|
||||||
|
showModal?: boolean;
|
||||||
setShowModal: (_: boolean) => void;
|
setShowModal: (_: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
@ -126,16 +127,18 @@ function PromptToast(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={chatStyle["prompt-toast"]} key="prompt-toast">
|
<div className={chatStyle["prompt-toast"]} key="prompt-toast">
|
||||||
<div
|
{props.showToast && (
|
||||||
className={chatStyle["prompt-toast-inner"] + " clickable"}
|
<div
|
||||||
role="button"
|
className={chatStyle["prompt-toast-inner"] + " clickable"}
|
||||||
onClick={() => props.setShowModal(true)}
|
role="button"
|
||||||
>
|
onClick={() => props.setShowModal(true)}
|
||||||
<BrainIcon />
|
>
|
||||||
<span className={chatStyle["prompt-toast-content"]}>
|
<BrainIcon />
|
||||||
{Locale.Context.Toast(context.length)}
|
<span className={chatStyle["prompt-toast-content"]}>
|
||||||
</span>
|
{Locale.Context.Toast(context.length)}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{props.showModal && (
|
{props.showModal && (
|
||||||
<div className="modal-mask">
|
<div className="modal-mask">
|
||||||
<Modal
|
<Modal
|
||||||
@ -187,6 +190,7 @@ function PromptToast(props: {
|
|||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
className={chatStyle["context-delete-button"]}
|
className={chatStyle["context-delete-button"]}
|
||||||
onClick={() => removeContextPrompt(i)}
|
onClick={() => removeContextPrompt(i)}
|
||||||
|
bordered
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -281,7 +285,7 @@ function useScrollToBottom() {
|
|||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const dom = scrollRef.current;
|
const dom = scrollRef.current;
|
||||||
if (dom && autoScroll) {
|
if (dom && autoScroll) {
|
||||||
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 500);
|
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -310,6 +314,12 @@ export function Chat(props: {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||||
const { scrollRef, setAutoScroll } = useScrollToBottom();
|
const { scrollRef, setAutoScroll } = useScrollToBottom();
|
||||||
|
const [hitBottom, setHitBottom] = useState(false);
|
||||||
|
|
||||||
|
const onChatBodyScroll = (e: HTMLElement) => {
|
||||||
|
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20;
|
||||||
|
setHitBottom(isTouchBottom);
|
||||||
|
};
|
||||||
|
|
||||||
// prompt hints
|
// prompt hints
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
@ -441,7 +451,7 @@ export function Chat(props: {
|
|||||||
role: "user",
|
role: "user",
|
||||||
content: userInput,
|
content: userInput,
|
||||||
date: new Date().toLocaleString(),
|
date: new Date().toLocaleString(),
|
||||||
preview: false,
|
preview: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
@ -505,12 +515,17 @@ export function Chat(props: {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PromptToast
|
<PromptToast
|
||||||
|
showToast={!hitBottom}
|
||||||
showModal={showPromptModal}
|
showModal={showPromptModal}
|
||||||
setShowModal={setShowPromptModal}
|
setShowModal={setShowPromptModal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["chat-body"]} ref={scrollRef}>
|
<div
|
||||||
|
className={styles["chat-body"]}
|
||||||
|
ref={scrollRef}
|
||||||
|
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||||
|
>
|
||||||
{messages.map((message, i) => {
|
{messages.map((message, i) => {
|
||||||
const isUser = message.role === "user";
|
const isUser = message.role === "user";
|
||||||
|
|
||||||
@ -533,6 +548,7 @@ export function Chat(props: {
|
|||||||
<div
|
<div
|
||||||
className={styles["chat-message-item"]}
|
className={styles["chat-message-item"]}
|
||||||
onMouseOver={() => inputRef.current?.blur()}
|
onMouseOver={() => inputRef.current?.blur()}
|
||||||
|
onTouchStart={() => inputRef.current?.blur()}
|
||||||
>
|
>
|
||||||
{!isUser &&
|
{!isUser &&
|
||||||
!(message.preview || message.content.length === 0) && (
|
!(message.preview || message.content.length === 0) && (
|
||||||
@ -612,7 +628,8 @@ export function Chat(props: {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<SendWhiteIcon />}
|
icon={<SendWhiteIcon />}
|
||||||
text={Locale.Chat.Send}
|
text={Locale.Chat.Send}
|
||||||
className={styles["chat-input-send"] + " no-dark"}
|
className={styles["chat-input-send"]}
|
||||||
|
noDark
|
||||||
onClick={onUserSubmit}
|
onClick={onUserSubmit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import "./window.scss";
|
@import "./window.scss";
|
||||||
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
@mixin container {
|
@mixin container {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
@ -73,7 +74,7 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -100%;
|
left: -100%;
|
||||||
z-index: 999;
|
z-index: 1000;
|
||||||
height: var(--full-height);
|
height: var(--full-height);
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@ -132,18 +133,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slide-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-item:hover {
|
.chat-item:hover {
|
||||||
background-color: var(--hover-color);
|
background-color: var(--hover-color);
|
||||||
}
|
}
|
||||||
@ -344,6 +333,7 @@
|
|||||||
.chat-input-panel {
|
.chat-input-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
padding-top: 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@ -24,18 +26,6 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slide-in {
|
|
||||||
from {
|
|
||||||
transform: translateY(10px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item {
|
.list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -35,7 +35,7 @@ const cn = {
|
|||||||
Download: "下载文件",
|
Download: "下载文件",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "上下文记忆 Prompt",
|
Title: "历史记忆",
|
||||||
EmptyContent: "尚未记忆",
|
EmptyContent: "尚未记忆",
|
||||||
Copy: "全部复制",
|
Copy: "全部复制",
|
||||||
},
|
},
|
||||||
|
@ -88,7 +88,11 @@ export async function requestUsage() {
|
|||||||
const response = (await res.json()) as {
|
const response = (await res.json()) as {
|
||||||
total_usage: number;
|
total_usage: number;
|
||||||
};
|
};
|
||||||
return Math.round(response.total_usage) / 100;
|
|
||||||
|
if (response.total_usage) {
|
||||||
|
response.total_usage = Math.round(response.total_usage) / 100;
|
||||||
|
}
|
||||||
|
return response.total_usage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Request usage] ", error, res.body);
|
console.error("[Request usage] ", error, res.body);
|
||||||
}
|
}
|
||||||
|
23
app/styles/animation.scss
Normal file
23
app/styles/animation.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@keyframes slide-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-from-top {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
@ -188,7 +188,7 @@ input[type="text"] {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
height: 36px;
|
min-height: 36px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
Loading…
Reference in New Issue
Block a user