resolve conflict

This commit is contained in:
dakai 2023-04-02 23:07:04 +08:00
commit 4dd2ef71fa
16 changed files with 71 additions and 5241 deletions

View File

@ -37,20 +37,22 @@ One-Click to deploy your own ChatGPT web UI.
- One-click export all chat history with full Markdown support - One-click export all chat history with full Markdown support
## 开发计划 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 界面文字自定义
## 开始使用 ## 开始使用
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys); 1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. 点击右侧按钮开始部署: 2. 点击右侧按钮开始部署:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登即可,记得在环境变量页填入 API Key [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登即可,记得在环境变量页填入 API Key
3. 部署完毕后,即可开始使用; 3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。 4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
@ -179,9 +181,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
``` ```

View File

@ -3,8 +3,10 @@ import { requestOpenai } from "../common";
async function makeRequest(req: NextRequest) { async function makeRequest(req: NextRequest) {
try { try {
const res = await requestOpenai(req); const api = await requestOpenai(req);
return new Response(res.body); const res = new NextResponse(api.body);
res.headers.set("Content-Type", "application/json");
return res;
} catch (e) { } catch (e) {
console.error("[OpenAI] ", req.body, e); console.error("[OpenAI] ", req.body, e);
return NextResponse.json( return NextResponse.json(

View File

@ -72,7 +72,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.mobile { .mobile {
display: none; display: none;
} }

View File

@ -170,9 +170,9 @@ function useSubmitHandler() {
const config = useChatStore((state) => state.config); const config = useChatStore((state) => state.config);
const submitKey = config.submitKey; const submitKey = config.submitKey;
const shouldSubmit = (e: KeyboardEvent) => { const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key !== "Enter") return false; if (e.key !== "Enter") return false;
if (e.key === "Enter" && e.nativeEvent.isComposing) return false;
return ( return (
(config.submitKey === SubmitKey.AltEnter && e.altKey) || (config.submitKey === SubmitKey.AltEnter && e.altKey) ||
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) || (config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
@ -294,7 +294,7 @@ export function Chat() {
}; };
// check if should send message // check if should send message
const onInputKeyDown = (e: KeyboardEvent) => { const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (shouldSubmit(e)) { if (shouldSubmit(e)) {
onUserSubmit(); onUserSubmit();
e.preventDefault(); e.preventDefault();
@ -330,6 +330,8 @@ export function Chat() {
const latestMessageRef = useRef<HTMLDivElement>(null); const latestMessageRef = useRef<HTMLDivElement>(null);
const [autoScroll, setAutoScroll] = useState(true); const [autoScroll, setAutoScroll] = useState(true);
const config = useChatStore((state) => state.config);
// preview messages // preview messages
const messages = (session.messages as RenderMessage[]) const messages = (session.messages as RenderMessage[])
.concat( .concat(
@ -345,13 +347,13 @@ export function Chat() {
: [], : [],
) )
.concat( .concat(
userInput.length > 0 userInput.length > 0 && config.sendPreviewBubble
? [ ? [
{ {
role: "user", role: "user",
content: userInput, content: userInput,
date: new Date().toLocaleString(), date: new Date().toLocaleString(),
preview: true, preview: false,
}, },
] ]
: [], : [],
@ -382,14 +384,7 @@ export function Chat() {
return ( return (
<div className={styles.chat} key={session.id}> <div className={styles.chat} key={session.id}>
<div className={styles["window-header"]}> <div className={styles["window-header"]}>
<div <div className={styles["window-header-title"]}>
className={styles["window-header-title"]}
//onClick={() => {
// if (window.innerWidth < 768) {
// setSideBarCollapse(!sidebarCollapse);
// }
//}}
>
<div <div
className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`} className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`}
onClick={() => { onClick={() => {
@ -532,7 +527,7 @@ export function Chat() {
rows={4} rows={4}
onInput={(e) => onInput(e.currentTarget.value)} onInput={(e) => onInput(e.currentTarget.value)}
value={userInput} value={userInput}
onKeyDown={(e) => onInputKeyDown(e as any)} onKeyDown={onInputKeyDown}
onFocus={() => setAutoScroll(true)} onFocus={() => setAutoScroll(true)}
onBlur={() => { onBlur={() => {
setAutoScroll(false); setAutoScroll(false);

View File

@ -49,14 +49,14 @@ function SettingItem(props: {
export function Settings(props: { closeSettings: () => void }) { export function Settings(props: { closeSettings: () => void }) {
const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData] = useChatStore( const [config, updateConfig, resetConfig, clearAllData, clearSessions] =
(state) => [ useChatStore((state) => [
state.config, state.config,
state.updateConfig, state.updateConfig,
state.resetConfig, state.resetConfig,
state.clearAllData, state.clearAllData,
], state.clearSessions,
); ]);
const updateStore = useUpdateStore(); const updateStore = useUpdateStore();
const [checkingUpdate, setCheckingUpdate] = useState(false); const [checkingUpdate, setCheckingUpdate] = useState(false);
@ -120,7 +120,7 @@ export function Settings(props: { closeSettings: () => void }) {
<div className={styles["window-action-button"]}> <div className={styles["window-action-button"]}>
<IconButton <IconButton
icon={<ClearIcon />} icon={<ClearIcon />}
onClick={clearAllData} onClick={clearSessions}
bordered bordered
title={Locale.Settings.Actions.ClearAll} title={Locale.Settings.Actions.ClearAll}
/> />
@ -278,6 +278,19 @@ export function Settings(props: { closeSettings: () => void }) {
} }
></input> ></input>
</SettingItem> </SettingItem>
<SettingItem title={Locale.Settings.SendPreviewBubble}>
<input
type="checkbox"
checked={config.sendPreviewBubble}
onChange={(e) =>
updateConfig(
(config) =>
(config.sendPreviewBubble = e.currentTarget.checked),
)
}
></input>
</SettingItem>
</List> </List>
<List> <List>
<SettingItem <SettingItem

View File

@ -77,6 +77,7 @@ const cn = {
SendKey: "发送键", SendKey: "发送键",
Theme: "主题", Theme: "主题",
TightBorder: "紧凑边框", TightBorder: "紧凑边框",
SendPreviewBubble: "发送预览气泡",
Prompt: { Prompt: {
Disable: { Disable: {
Title: "禁用提示词自动补全", Title: "禁用提示词自动补全",

View File

@ -78,6 +78,7 @@ const en: LocaleType = {
SendKey: "Send Key", SendKey: "Send Key",
Theme: "Theme", Theme: "Theme",
TightBorder: "Tight Border", TightBorder: "Tight Border",
SendPreviewBubble: "Send Preview Bubble",
Prompt: { Prompt: {
Disable: { Disable: {
Title: "Disable auto-completion", Title: "Disable auto-completion",

View File

@ -78,6 +78,7 @@ const es: LocaleType = {
SendKey: "Tecla de envío", SendKey: "Tecla de envío",
Theme: "Tema", Theme: "Tema",
TightBorder: "Borde ajustado", TightBorder: "Borde ajustado",
SendPreviewBubble: "Send preview bubble",
Prompt: { Prompt: {
Disable: { Disable: {
Title: "Desactivar autocompletado", Title: "Desactivar autocompletado",

View File

@ -77,6 +77,7 @@ const tw: LocaleType = {
SendKey: "發送鍵", SendKey: "發送鍵",
Theme: "主題", Theme: "主題",
TightBorder: "緊湊邊框", TightBorder: "緊湊邊框",
SendPreviewBubble: "發送預覽氣泡",
Prompt: { Prompt: {
Disable: { Disable: {
Title: "停用提示詞自動補全", Title: "停用提示詞自動補全",

View File

@ -2,6 +2,10 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing";
import { filterConfig, Message, ModelConfig, useAccessStore } from "./store"; import { filterConfig, Message, ModelConfig, useAccessStore } from "./store";
import Locale from "./locales"; import Locale from "./locales";
if (!Array.prototype.at) {
require("array.prototype.at/auto");
}
const TIME_OUT_MS = 30000; const TIME_OUT_MS = 30000;
const makeRequestParam = ( const makeRequestParam = (

View File

@ -11,6 +11,10 @@ import { trimTopic } from "../utils";
import Locale from "../locales"; import Locale from "../locales";
if (!Array.prototype.at) {
require("array.prototype.at/auto");
}
export type Message = ChatCompletionResponseMessage & { export type Message = ChatCompletionResponseMessage & {
date: string; date: string;
streaming?: boolean; streaming?: boolean;
@ -39,6 +43,7 @@ export interface ChatConfig {
fontSize: number; fontSize: number;
theme: Theme; theme: Theme;
tightBorder: boolean; tightBorder: boolean;
sendPreviewBubble: boolean;
disablePromptHint: boolean; disablePromptHint: boolean;
@ -128,6 +133,7 @@ const DEFAULT_CONFIG: ChatConfig = {
fontSize: 14, fontSize: 14,
theme: Theme.Auto as Theme, theme: Theme.Auto as Theme,
tightBorder: false, tightBorder: false,
sendPreviewBubble: true,
disablePromptHint: false, disablePromptHint: false,
@ -185,6 +191,7 @@ interface ChatStore {
config: ChatConfig; config: ChatConfig;
sessions: ChatSession[]; sessions: ChatSession[];
currentSessionIndex: number; currentSessionIndex: number;
clearSessions: () => void;
removeSession: (index: number) => void; removeSession: (index: number) => void;
selectSession: (index: number) => void; selectSession: (index: number) => void;
newSession: () => void; newSession: () => void;
@ -225,6 +232,12 @@ export const useChatStore = create<ChatStore>()(
...DEFAULT_CONFIG, ...DEFAULT_CONFIG,
}, },
clearSessions() {
set(() => ({
sessions: [createEmptySession()],
currentSessionIndex: 0,
}));
},
resetConfig() { resetConfig() {
set(() => ({ config: { ...DEFAULT_CONFIG } })); set(() => ({ config: { ...DEFAULT_CONFIG } }));
}, },
@ -374,7 +387,7 @@ export const useChatStore = create<ChatStore>()(
const config = get().config; const config = get().config;
const n = session.messages.length; const n = session.messages.length;
const recentMessages = session.messages.slice( const recentMessages = session.messages.slice(
n - config.historyMessageCount, Math.max(0, n - config.historyMessageCount),
); );
const memoryPrompt = get().getMemoryPrompt(); const memoryPrompt = get().getMemoryPrompt();

View File

@ -99,19 +99,19 @@ export const usePromptStore = create<PromptStore>()(
({ ({
title, title,
content, content,
} as Prompt) } as Prompt),
); );
}) })
.concat([...(state?.prompts?.values() ?? [])]); .concat([...(state?.prompts?.values() ?? [])]);
const allPromptsForSearch = builtinPrompts.reduce( const allPromptsForSearch = builtinPrompts.reduce(
(pre, cur) => pre.concat(cur), (pre, cur) => pre.concat(cur),
[] [],
); );
SearchService.count.builtin = res.en.length + res.cn.length; SearchService.count.builtin = res.en.length + res.cn.length;
SearchService.init(allPromptsForSearch); SearchService.init(allPromptsForSearch);
}); });
}, },
} },
) ),
); );

View File

@ -51,10 +51,15 @@
--window-width: 90vw; --window-width: 90vw;
--window-height: 90vh; --window-height: 90vh;
--sidebar-width: 300px; --sidebar-width: 300px;
<<<<<<< HEAD
--sidebar-collapse-width: 75px; --sidebar-collapse-width: 75px;
--window-content-width: calc(100% - var(--sidebar-width)); --window-content-width: calc(100% - var(--sidebar-width));
--window-content-width-collapse: calc(100% - var(--sidebar-collapse-width)); --window-content-width-collapse: calc(100% - var(--sidebar-collapse-width));
--message-max-width: 90%; --message-max-width: 90%;
=======
--window-content-width: calc(100% - var(--sidebar-width));
--message-max-width: 80%;
>>>>>>> a6c598c0172ee0121b92e7a114c7145846614006
--full-height: 100%; --full-height: 100%;
} }

View File

@ -2,15 +2,7 @@ import { showToast } from "./components/ui-lib";
import Locale from "./locales"; import Locale from "./locales";
export function trimTopic(topic: string) { export function trimTopic(topic: string) {
const s = topic.split(""); return topic.replace(/[,。!?、,.!?]*$/, "");
let lastChar = s.at(-1); // 获取 s 的最后一个字符
let pattern = /[,。!?、,.!?]/; // 定义匹配中文和英文标点符号的正则表达式
while (lastChar && pattern.test(lastChar!)) {
s.pop();
lastChar = s.at(-1);
}
return s.join("");
} }
export function copyToClipboard(text: string) { export function copyToClipboard(text: string) {

View File

@ -23,9 +23,9 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"remark-breaks": "^3.0.2",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"rehype-prism-plus": "^1.5.1", "rehype-prism-plus": "^1.5.1",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"sass": "^1.59.2", "sass": "^1.59.2",
@ -39,6 +39,7 @@
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-katex": "^3.0.0", "@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.2", "@types/spark-md5": "^3.0.2",
"array.prototype.at": "^1.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.36.0", "eslint": "^8.36.0",
"eslint-config-next": "13.2.3", "eslint-config-next": "13.2.3",

5201
yarn.lock

File diff suppressed because it is too large Load Diff