diff --git a/README.md b/README.md index 9f54194a..90ed7d42 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel. ## 最新动态 - 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。 +- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com ## Get Started @@ -167,7 +168,13 @@ Specify OpenAI organization ID. > Default: Empty -If you do not want users to input their own API key, set this environment variable to 1. +If you do not want users to input their own API key, set this value to 1. + +### `DISABLE_GPT4` (optional) + +> Default: Empty + +If you do not want users to use GPT-4, set this value to 1. ## Development @@ -255,6 +262,9 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s [@WingCH](https://github.com/WingCH) [@jtung4](https://github.com/jtung4) [@micozhu](https://github.com/micozhu) +[@jhansion](https://github.com/jhansion) +[@Sha1rholder](https://github.com/Sha1rholder) +[@AnsonHyq](https://github.com/AnsonHyq) ### Contributor diff --git a/README_CN.md b/README_CN.md index 1da68f65..9601e5fd 100644 --- a/README_CN.md +++ b/README_CN.md @@ -64,7 +64,7 @@ code1,code2,code3 ## 环境变量 -> 本项目大多数配置项都通过环境变量来设置。 +> 本项目大多数配置项都通过环境变量来设置,教程:[如何修改 Vercel 环境变量](./docs/vercel-cn.md)。 ### `OPENAI_API_KEY` (必填项) @@ -94,6 +94,10 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填 如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。 +### `DISABLE_GPT4` (可选) + +如果你不想让用户使用 GPT-4,将此环境变量设置为 1 即可。 + ## 开发 > 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。 diff --git a/app/api/config/route.ts b/app/api/config/route.ts index 62c8d60f..2b3bcbf2 100644 --- a/app/api/config/route.ts +++ b/app/api/config/route.ts @@ -1,4 +1,4 @@ -import { NextRequest, NextResponse } from "next/server"; +import { NextResponse } from "next/server"; import { getServerSideConfig } from "../../config/server"; @@ -9,6 +9,7 @@ const serverConfig = getServerSideConfig(); const DANGER_CONFIG = { needCode: serverConfig.needCode, hideUserApiKey: serverConfig.hideUserApiKey, + enableGPT4: serverConfig.enableGPT4, }; declare global { diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 8786877b..54def01c 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -53,7 +53,7 @@ import chatStyle from "./chat.module.scss"; import { ListItem, Modal, showModal } from "./ui-lib"; import { useLocation, useNavigate } from "react-router-dom"; -import { Path } from "../constant"; +import { LAST_INPUT_KEY, Path } from "../constant"; import { Avatar } from "./emoji"; import { MaskAvatar, MaskConfig } from "./mask"; import { useMaskStore } from "../store/mask"; @@ -404,7 +404,6 @@ export function Chat() { const inputRef = useRef(null); const [userInput, setUserInput] = useState(""); - const [beforeInput, setBeforeInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const { submitKey, shouldSubmit } = useSubmitHandler(); const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom(); @@ -477,7 +476,7 @@ export function Chat() { if (userInput.trim() === "") return; setIsLoading(true); chatStore.onUserInput(userInput).then(() => setIsLoading(false)); - setBeforeInput(userInput); + localStorage.setItem(LAST_INPUT_KEY, userInput); setUserInput(""); setPromptHints([]); if (!isMobileScreen) inputRef.current?.focus(); @@ -491,9 +490,9 @@ export function Chat() { // check if should send message const onInputKeyDown = (e: React.KeyboardEvent) => { - // if ArrowUp and no userInput + // if ArrowUp and no userInput, fill with last input if (e.key === "ArrowUp" && userInput.length <= 0) { - setUserInput(beforeInput); + setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? ""); e.preventDefault(); return; } @@ -503,11 +502,6 @@ export function Chat() { } }; const onRightClick = (e: any, message: Message) => { - // auto fill user input - if (message.role === "user") { - setUserInput(message.content); - } - // copy to clipboard if (selectOrCopy(e.currentTarget, message.content)) { e.preventDefault(); diff --git a/app/components/mask.tsx b/app/components/mask.tsx index 9794c974..a37cfba7 100644 --- a/app/components/mask.tsx +++ b/app/components/mask.tsx @@ -14,7 +14,7 @@ import CopyIcon from "../icons/copy.svg"; import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask"; import { Message, ModelConfig, ROLES, useChatStore } from "../store"; -import { Input, List, ListItem, Modal, Popover, showToast } from "./ui-lib"; +import { Input, List, ListItem, Modal, Popover } from "./ui-lib"; import { Avatar, AvatarPicker } from "./emoji"; import Locale, { AllLangs, Lang } from "../locales"; import { useNavigate } from "react-router-dom"; diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index e3273925..63614ffa 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -32,6 +32,28 @@ const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { loading: () => null, }); +function useHotKey() { + const chatStore = useChatStore(); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.metaKey || e.altKey || e.ctrlKey) { + const n = chatStore.sessions.length; + const limit = (x: number) => (x + n) % n; + const i = chatStore.currentSessionIndex; + if (e.key === "ArrowUp") { + chatStore.selectSession(limit(i - 1)); + } else if (e.key === "ArrowDown") { + chatStore.selectSession(limit(i + 1)); + } + } + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }); +} + function useDragSideBar() { const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); @@ -86,9 +108,10 @@ export function SideBar(props: { className?: string }) { // drag side bar const { onDragMouseDown, shouldNarrow } = useDragSideBar(); const navigate = useNavigate(); - const config = useAppConfig(); + useHotKey(); + return (
{ proxyUrl: process.env.PROXY_URL, isVercel: !!process.env.VERCEL, hideUserApiKey: !!process.env.HIDE_USER_API_KEY, + enableGPT4: !process.env.DISABLE_GPT4, }; }; diff --git a/app/constant.ts b/app/constant.ts index fed20caf..d0f9fc74 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -38,3 +38,5 @@ export const MIN_SIDEBAR_WIDTH = 230; export const NARROW_SIDEBAR_WIDTH = 100; export const ACCESS_CODE_PREFIX = "ak-"; + +export const LAST_INPUT_KEY = "last-input"; diff --git a/app/masks/en.ts b/app/masks/en.ts index 93e9bd6a..af4f215c 100644 --- a/app/masks/en.ts +++ b/app/masks/en.ts @@ -31,7 +31,7 @@ export const EN_MASKS: BuiltinMask[] = [ ], modelConfig: { model: "gpt-4", - temperature: 1, + temperature: 0.5, max_tokens: 2000, presence_penalty: 0, sendMemory: true, diff --git a/app/store/access.ts b/app/store/access.ts index 51290d0a..79b7b990 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -2,6 +2,7 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { StoreKey } from "../constant"; import { BOT_HELLO } from "./chat"; +import { ALL_MODELS } from "./config"; export interface AccessControlStore { accessCode: string; @@ -60,6 +61,14 @@ export const useAccessStore = create()( console.log("[Config] got config from server", res); set(() => ({ ...res })); + if (!res.enableGPT4) { + ALL_MODELS.forEach((model) => { + if (model.name.startsWith("gpt-4")) { + (model as any).available = false; + } + }); + } + if ((res as any).botHello) { BOT_HELLO.content = (res as any).botHello; } diff --git a/app/store/chat.ts b/app/store/chat.ts index c938d787..cb11087d 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -180,8 +180,9 @@ export const useChatStore = create()( const sessions = get().sessions.slice(); sessions.splice(index, 1); + const currentIndex = get().currentSessionIndex; let nextIndex = Math.min( - get().currentSessionIndex, + currentIndex - Number(index < currentIndex), sessions.length - 1, ); @@ -251,9 +252,20 @@ export const useChatStore = create()( model: modelConfig.model, }); + const systemInfo = createMessage({ + role: "system", + content: `IMPRTANT: You are a virtual assistant powered by the ${ + modelConfig.model + } model, now time is ${new Date().toLocaleString()}}`, + id: botMessage.id! + 1, + }); + // get recent messages + const systemMessages = [systemInfo]; const recentMessages = get().getMessagesWithMemory(); - const sendMessages = recentMessages.concat(userMessage); + const sendMessages = systemMessages.concat( + recentMessages.concat(userMessage), + ); const sessionIndex = get().currentSessionIndex; const messageIndex = get().currentSession().messages.length + 1; diff --git a/app/store/config.ts b/app/store/config.ts index 926c296f..1e960456 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -76,6 +76,26 @@ export const ALL_MODELS = [ name: "gpt-3.5-turbo-0301", available: true, }, + { + name: "qwen-v1", // 通义千问 + available: false, + }, + { + name: "ernie", // 文心一言 + available: false, + }, + { + name: "spark", // 讯飞星火 + available: false, + }, + { + name: "llama", // llama + available: false, + }, + { + name: "chatglm", // chatglm-6b + available: false, + }, ] as const; export type ModelType = (typeof ALL_MODELS)[number]["name"]; diff --git a/app/utils.ts b/app/utils.ts index 43ea796e..1e34a6b3 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -160,13 +160,13 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) { measureDom.style.width = width + "px"; measureDom.innerText = dom.value.trim().length > 0 ? dom.value : "1"; - const lineWrapCount = Math.max(0, dom.value.split("\n").length - 1); + const emptyLineWrap = Math.max(0, dom.value.split("\n\n").length - 1); const height = parseFloat(window.getComputedStyle(measureDom).height); const singleLineHeight = parseFloat( window.getComputedStyle(singleLineDom).height, ); - const rows = Math.round(height / singleLineHeight) + lineWrapCount; + const rows = Math.round(height / singleLineHeight) + emptyLineWrap; return rows; }