diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 52feae50e..15d324074 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -31,3 +31,10 @@ jobs: # Set test_mode true to run tests instead of the true action!! test_mode: false + + - name: Sync check + if: failure() + run: | + echo "::error::由于权限不足,导致同步失败(这是预期的行为),请前往仓库首页手动执行[Sync fork]。" + echo "::error::Due to insufficient permissions, synchronization failed (as expected). Please go to the repository homepage and manually perform [Sync fork]." + exit 1 diff --git a/README.md b/README.md index bb0278437..693038cb7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel. - **Deploy for free with one-click** on Vercel in under 1 minute - Privacy first, all data stored locally in the browser - Responsive design, dark mode and PWA -- Fast first screen loading speed (~100kb) +- Fast first screen loading speed (~100kb), support streaming response - 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 - One-click export all chat history with full Markdown support @@ -49,7 +49,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel. - 在 1 分钟内使用 Vercel **免费一键部署** - 精心设计的 UI,响应式设计,支持深色模式,支持 PWA -- 极快的首屏加载速度(~100kb) +- 极快的首屏加载速度(~100kb),支持流式响应 - 隐私安全,所有数据保存在用户浏览器本地 - 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts) - 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话 @@ -213,6 +213,8 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s ### Sponsor +> 仅列出捐赠金额 >= 100RMB 的用户。 + [@mushan0x0](https://github.com/mushan0x0) [@ClarenceDan](https://github.com/ClarenceDan) [@zhangjia](https://github.com/zhangjia) @@ -222,6 +224,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s [@webees](https://github.com/webees) [@chazzhou](https://github.com/chazzhou) [@hauy](https://github.com/hauy) +[@Corwin006](https://github.com/Corwin006) ### Contributor diff --git a/README_CN.md b/README_CN.md index db08ee445..b8a46f20a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -153,12 +153,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s ### 捐赠者 -> 仅列出了部分大额打赏,小额打赏(< 100RMB)人数太多,在此不再列出,敬请谅解。 - -[@mushan0x0](https://github.com/mushan0x0) -[@ClarenceDan](https://github.com/ClarenceDan) -[@zhangjia](https://github.com/zhangjia) -[@hoochanlon](https://github.com/hoochanlon) +> 见英文版。 ### 贡献者 diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index f57e6c100..7cd2889f7 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -1,5 +1,29 @@ @import "../styles/animation.scss"; +.chat-input-actions { + display: flex; + flex-wrap: wrap; + + .chat-input-action { + display: inline-flex; + border-radius: 20px; + font-size: 12px; + background-color: var(--white); + color: var(--black); + border: var(--border-in-light); + padding: 4px 10px; + animation: slide-in ease 0.3s; + box-shadow: var(--card-shadow); + transition: all ease 0.3s; + margin-bottom: 10px; + align-items: center; + + &:not(:last-child) { + margin-right: 5px; + } + } +} + .prompt-toast { position: absolute; bottom: -50px; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 03f905ecf..b9ae13926 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -14,6 +14,11 @@ import DeleteIcon from "../icons/delete.svg"; import MaxIcon from "../icons/max.svg"; import MinIcon from "../icons/min.svg"; +import LightIcon from "../icons/light.svg"; +import DarkIcon from "../icons/dark.svg"; +import AutoIcon from "../icons/auto.svg"; +import BottomIcon from "../icons/bottom.svg"; + import { Message, SubmitKey, @@ -22,6 +27,7 @@ import { ROLES, createMessage, useAccessStore, + Theme, } from "../store"; import { @@ -31,6 +37,7 @@ import { isMobileScreen, selectOrCopy, autoGrowTextArea, + getCSSVar, } from "../utils"; import dynamic from "next/dynamic"; @@ -60,7 +67,11 @@ export function Avatar(props: { role: Message["role"] }) { const config = useChatStore((state) => state.config); if (props.role !== "user") { - return ; + return ( +
+ +
+ ); } return ( @@ -316,22 +327,78 @@ function useScrollToBottom() { // for auto-scroll const scrollRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); + const scrollToBottom = () => { + const dom = scrollRef.current; + if (dom) { + setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1); + } + }; // auto scroll useLayoutEffect(() => { - const dom = scrollRef.current; - if (dom && autoScroll) { - setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1); - } + autoScroll && scrollToBottom(); }); return { scrollRef, autoScroll, setAutoScroll, + scrollToBottom, }; } +export function ChatActions(props: { + showPromptModal: () => void; + scrollToBottom: () => void; + hitBottom: boolean; +}) { + const chatStore = useChatStore(); + + const theme = chatStore.config.theme; + + function nextTheme() { + const themes = [Theme.Auto, Theme.Light, Theme.Dark]; + const themeIndex = themes.indexOf(theme); + const nextIndex = (themeIndex + 1) % themes.length; + const nextTheme = themes[nextIndex]; + chatStore.updateConfig((config) => (config.theme = nextTheme)); + } + + return ( +
+ {!props.hitBottom && ( +
+ +
+ )} + {props.hitBottom && ( +
+ +
+ )} + +
+ {theme === Theme.Auto ? ( + + ) : theme === Theme.Light ? ( + + ) : theme === Theme.Dark ? ( + + ) : null} +
+
+ ); +} + export function Chat(props: { showSideBar?: () => void; sideBarShowing?: boolean; @@ -350,11 +417,10 @@ export function Chat(props: { const [beforeInput, setBeforeInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const { submitKey, shouldSubmit } = useSubmitHandler(); - const { scrollRef, setAutoScroll } = useScrollToBottom(); + const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); const onChatBodyScroll = (e: HTMLElement) => { - setAutoScroll(false); const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; setHitBottom(isTouchBottom); }; @@ -673,22 +739,20 @@ export function Chat(props: { )} - {(message.preview || message.content.length === 0) && - !isUser ? ( - - ) : ( -
onRightClick(e, message)} - onDoubleClickCapture={() => { - if (!isMobileScreen()) return; - setUserInput(message.content); - }} - > - -
- )} + onRightClick(e, message)} + onDoubleClickCapture={() => { + if (!isMobileScreen()) return; + setUserInput(message.content); + }} + fontSize={fontSize} + parentRef={scrollRef} + /> {!isUser && !message.preview && (
@@ -705,6 +769,12 @@ export function Chat(props: {
+ + setShowPromptModal(true)} + scrollToBottom={scrollToBottom} + hitBottom={hitBottom} + />