mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	@@ -26,13 +26,13 @@
 | 
			
		||||
@media only screen and (min-width: 600px) {
 | 
			
		||||
  .tight-container {
 | 
			
		||||
    --window-width: 100vw;
 | 
			
		||||
    --window-height: 100vh;
 | 
			
		||||
    --window-height: var(--full-height);
 | 
			
		||||
    --window-content-width: calc(100% - var(--sidebar-width));
 | 
			
		||||
 | 
			
		||||
    @include container();
 | 
			
		||||
 | 
			
		||||
    max-width: 100vw;
 | 
			
		||||
    max-height: 100vh;
 | 
			
		||||
    max-height: var(--full-height);
 | 
			
		||||
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
  }
 | 
			
		||||
@@ -74,7 +74,7 @@
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: -100%;
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    height: var(--full-height);
 | 
			
		||||
    transition: all ease 0.3s;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,13 @@ import DownloadIcon from "../icons/download.svg";
 | 
			
		||||
 | 
			
		||||
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
 | 
			
		||||
import { showModal, showToast } from "./ui-lib";
 | 
			
		||||
import { copyToClipboard, downloadAs, isIOS, selectOrCopy } from "../utils";
 | 
			
		||||
import {
 | 
			
		||||
  copyToClipboard,
 | 
			
		||||
  downloadAs,
 | 
			
		||||
  isIOS,
 | 
			
		||||
  isMobileScreen,
 | 
			
		||||
  selectOrCopy,
 | 
			
		||||
} from "../utils";
 | 
			
		||||
import Locale from "../locales";
 | 
			
		||||
 | 
			
		||||
import dynamic from "next/dynamic";
 | 
			
		||||
@@ -102,7 +108,7 @@ export function ChatList() {
 | 
			
		||||
      state.currentSessionIndex,
 | 
			
		||||
      state.selectSession,
 | 
			
		||||
      state.removeSession,
 | 
			
		||||
    ]
 | 
			
		||||
    ],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@@ -196,7 +202,7 @@ export function Chat(props: {
 | 
			
		||||
      setPromptHints(promptStore.search(text));
 | 
			
		||||
    },
 | 
			
		||||
    100,
 | 
			
		||||
    { leading: true, trailing: true }
 | 
			
		||||
    { leading: true, trailing: true },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const onPromptSelect = (prompt: Prompt) => {
 | 
			
		||||
@@ -210,7 +216,7 @@ export function Chat(props: {
 | 
			
		||||
    if (!dom) return;
 | 
			
		||||
    const paddingBottomNum: number = parseInt(
 | 
			
		||||
      window.getComputedStyle(dom).paddingBottom,
 | 
			
		||||
      10
 | 
			
		||||
      10,
 | 
			
		||||
    );
 | 
			
		||||
    dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
 | 
			
		||||
  };
 | 
			
		||||
@@ -284,9 +290,7 @@ export function Chat(props: {
 | 
			
		||||
 | 
			
		||||
  // for auto-scroll
 | 
			
		||||
  const latestMessageRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
 | 
			
		||||
  // wont scroll while hovering messages
 | 
			
		||||
  const [autoScroll, setAutoScroll] = useState(false);
 | 
			
		||||
  const [autoScroll, setAutoScroll] = useState(true);
 | 
			
		||||
 | 
			
		||||
  // preview messages
 | 
			
		||||
  const messages = (session.messages as RenderMessage[])
 | 
			
		||||
@@ -300,7 +304,7 @@ export function Chat(props: {
 | 
			
		||||
              preview: true,
 | 
			
		||||
            },
 | 
			
		||||
          ]
 | 
			
		||||
        : []
 | 
			
		||||
        : [],
 | 
			
		||||
    )
 | 
			
		||||
    .concat(
 | 
			
		||||
      userInput.length > 0
 | 
			
		||||
@@ -312,14 +316,24 @@ export function Chat(props: {
 | 
			
		||||
              preview: true,
 | 
			
		||||
            },
 | 
			
		||||
          ]
 | 
			
		||||
        : []
 | 
			
		||||
        : [],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  // auto scroll
 | 
			
		||||
  useLayoutEffect(() => {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      const dom = latestMessageRef.current;
 | 
			
		||||
      if (dom && !isIOS() && autoScroll) {
 | 
			
		||||
      const inputDom = inputRef.current;
 | 
			
		||||
 | 
			
		||||
      // only scroll when input overlaped message body
 | 
			
		||||
      let shouldScroll = true;
 | 
			
		||||
      if (dom && inputDom) {
 | 
			
		||||
        const domRect = dom.getBoundingClientRect();
 | 
			
		||||
        const inputRect = inputDom.getBoundingClientRect();
 | 
			
		||||
        shouldScroll = domRect.top > inputRect.top;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dom && autoScroll && shouldScroll) {
 | 
			
		||||
        dom.scrollIntoView({
 | 
			
		||||
          block: "end",
 | 
			
		||||
        });
 | 
			
		||||
@@ -340,7 +354,7 @@ export function Chat(props: {
 | 
			
		||||
              const newTopic = prompt(Locale.Chat.Rename, session.topic);
 | 
			
		||||
              if (newTopic && newTopic !== session.topic) {
 | 
			
		||||
                chatStore.updateCurrentSession(
 | 
			
		||||
                  (session) => (session.topic = newTopic!)
 | 
			
		||||
                  (session) => (session.topic = newTopic!),
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
@@ -475,7 +489,7 @@ export function Chat(props: {
 | 
			
		||||
            onFocus={() => setAutoScroll(true)}
 | 
			
		||||
            onBlur={() => {
 | 
			
		||||
              setAutoScroll(false);
 | 
			
		||||
              setTimeout(() => setPromptHints([]), 100);
 | 
			
		||||
              setTimeout(() => setPromptHints([]), 500);
 | 
			
		||||
            }}
 | 
			
		||||
            autoFocus={!props?.sideBarShowing}
 | 
			
		||||
          />
 | 
			
		||||
@@ -586,7 +600,7 @@ export function Home() {
 | 
			
		||||
      state.newSession,
 | 
			
		||||
      state.currentSessionIndex,
 | 
			
		||||
      state.removeSession,
 | 
			
		||||
    ]
 | 
			
		||||
    ],
 | 
			
		||||
  );
 | 
			
		||||
  const loading = !useHasHydrated();
 | 
			
		||||
  const [showSideBar, setShowSideBar] = useState(true);
 | 
			
		||||
@@ -604,7 +618,9 @@ export function Home() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={`${
 | 
			
		||||
        config.tightBorder ? styles["tight-container"] : styles.container
 | 
			
		||||
        config.tightBorder && !isMobileScreen()
 | 
			
		||||
          ? styles["tight-container"]
 | 
			
		||||
          : styles.container
 | 
			
		||||
      }`}
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import {
 | 
			
		||||
import { Avatar, PromptHints } from "./home";
 | 
			
		||||
 | 
			
		||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
 | 
			
		||||
import { getCurrentCommitId } from "../utils";
 | 
			
		||||
import { getCurrentVersion } from "../utils";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { UPDATE_URL } from "../constant";
 | 
			
		||||
import { SearchService, usePromptStore } from "../store/prompt";
 | 
			
		||||
@@ -60,7 +60,7 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
 | 
			
		||||
  const updateStore = useUpdateStore();
 | 
			
		||||
  const [checkingUpdate, setCheckingUpdate] = useState(false);
 | 
			
		||||
  const currentId = getCurrentCommitId();
 | 
			
		||||
  const currentId = getCurrentVersion();
 | 
			
		||||
  const remoteId = updateStore.remoteId;
 | 
			
		||||
  const hasNewVersion = currentId !== remoteId;
 | 
			
		||||
 | 
			
		||||
@@ -267,19 +267,17 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
            ></input>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
 | 
			
		||||
          <div className="no-mobile">
 | 
			
		||||
            <SettingItem title={Locale.Settings.TightBorder}>
 | 
			
		||||
              <input
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                checked={config.tightBorder}
 | 
			
		||||
                onChange={(e) =>
 | 
			
		||||
                  updateConfig(
 | 
			
		||||
                    (config) => (config.tightBorder = e.currentTarget.checked),
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              ></input>
 | 
			
		||||
            </SettingItem>
 | 
			
		||||
          </div>
 | 
			
		||||
          <SettingItem title={Locale.Settings.TightBorder}>
 | 
			
		||||
            <input
 | 
			
		||||
              type="checkbox"
 | 
			
		||||
              checked={config.tightBorder}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) => (config.tightBorder = e.currentTarget.checked),
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></input>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
        </List>
 | 
			
		||||
        <List>
 | 
			
		||||
          <SettingItem
 | 
			
		||||
 
 | 
			
		||||
@@ -3,3 +3,4 @@ export const REPO = "ChatGPT-Next-Web";
 | 
			
		||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
 | 
			
		||||
export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
 | 
			
		||||
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
 | 
			
		||||
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access";
 | 
			
		||||
let COMMIT_ID: string | undefined;
 | 
			
		||||
try {
 | 
			
		||||
  COMMIT_ID = process
 | 
			
		||||
    .execSync("git rev-parse --short HEAD")
 | 
			
		||||
    .execSync("git describe --tags --abbrev=0")
 | 
			
		||||
    .toString()
 | 
			
		||||
    .trim();
 | 
			
		||||
} catch (e) {
 | 
			
		||||
  console.error("No git or not from git repo.")
 | 
			
		||||
  console.error("No git or not from git repo.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const metadata = {
 | 
			
		||||
@@ -22,13 +22,13 @@ export const metadata = {
 | 
			
		||||
    title: "ChatGPT Next Web",
 | 
			
		||||
    statusBarStyle: "black-translucent",
 | 
			
		||||
  },
 | 
			
		||||
  themeColor: "#fafafa"
 | 
			
		||||
  themeColor: "#fafafa",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Meta() {
 | 
			
		||||
  const metas = {
 | 
			
		||||
    version: COMMIT_ID ?? "unknown",
 | 
			
		||||
    access: (ACCESS_CODES.size > 0 || IS_IN_DOCKER) ? "enabled" : "disabled",
 | 
			
		||||
    access: ACCESS_CODES.size > 0 || IS_IN_DOCKER ? "enabled" : "disabled",
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import { FETCH_COMMIT_URL } from "../constant";
 | 
			
		||||
import { getCurrentCommitId } from "../utils";
 | 
			
		||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
 | 
			
		||||
import { getCurrentVersion } from "../utils";
 | 
			
		||||
 | 
			
		||||
export interface UpdateStore {
 | 
			
		||||
  lastUpdate: number;
 | 
			
		||||
@@ -19,16 +19,15 @@ export const useUpdateStore = create<UpdateStore>()(
 | 
			
		||||
      remoteId: "",
 | 
			
		||||
 | 
			
		||||
      async getLatestCommitId(force = false) {
 | 
			
		||||
        const overOneHour = Date.now() - get().lastUpdate > 3600 * 1000;
 | 
			
		||||
        const shouldFetch = force || overOneHour;
 | 
			
		||||
        const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
 | 
			
		||||
        const shouldFetch = force || overTenMins;
 | 
			
		||||
        if (!shouldFetch) {
 | 
			
		||||
          return getCurrentCommitId();
 | 
			
		||||
          return getCurrentVersion();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          const data = await (await fetch(FETCH_COMMIT_URL)).json();
 | 
			
		||||
          const sha = data[0].sha as string;
 | 
			
		||||
          const remoteId = sha.substring(0, 7);
 | 
			
		||||
          const data = await (await fetch(FETCH_TAG_URL)).json();
 | 
			
		||||
          const remoteId = data[0].name as string;
 | 
			
		||||
          set(() => ({
 | 
			
		||||
            lastUpdate: Date.now(),
 | 
			
		||||
            remoteId,
 | 
			
		||||
@@ -37,13 +36,13 @@ export const useUpdateStore = create<UpdateStore>()(
 | 
			
		||||
          return remoteId;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          console.error("[Fetch Upstream Commit Id]", error);
 | 
			
		||||
          return getCurrentCommitId();
 | 
			
		||||
          return getCurrentVersion();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: UPDATE_KEY,
 | 
			
		||||
      version: 1,
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -53,12 +53,13 @@
 | 
			
		||||
  --sidebar-width: 300px;
 | 
			
		||||
  --window-content-width: calc(100% - var(--sidebar-width));
 | 
			
		||||
  --message-max-width: 80%;
 | 
			
		||||
  --full-height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 600px) {
 | 
			
		||||
  :root {
 | 
			
		||||
    --window-width: 100vw;
 | 
			
		||||
    --window-height: 100vh;
 | 
			
		||||
    --window-height: var(--full-height);
 | 
			
		||||
    --sidebar-width: 100vw;
 | 
			
		||||
    --window-content-width: var(--window-width);
 | 
			
		||||
    --message-max-width: 100%;
 | 
			
		||||
@@ -80,14 +81,14 @@ body {
 | 
			
		||||
  color: var(--black);
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  height: var(--full-height);
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  font-family: "Noto Sans SC", "SF Pro SC", "SF Pro Text", "SF Pro Icons",
 | 
			
		||||
  "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
 | 
			
		||||
    "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
 | 
			
		||||
 | 
			
		||||
  @media only screen and (max-width: 600px) {
 | 
			
		||||
    background-color: var(--second);
 | 
			
		||||
@@ -119,6 +120,11 @@ select {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background-color: var(--white);
 | 
			
		||||
  color: var(--black);
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="checkbox"] {
 | 
			
		||||
@@ -196,7 +202,7 @@ div.math {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  height: var(--full-height);
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  background-color: rgba($color: #000000, $alpha: 0.5);
 | 
			
		||||
  display: flex;
 | 
			
		||||
 
 | 
			
		||||
@@ -120,33 +120,3 @@
 | 
			
		||||
    cursor: help;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin light {
 | 
			
		||||
  .markdown-body pre {
 | 
			
		||||
    filter: invert(1) hue-rotate(90deg) brightness(1.3);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin dark {
 | 
			
		||||
  .markdown-body pre {
 | 
			
		||||
    filter: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  @include light();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.light {
 | 
			
		||||
  @include light();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dark {
 | 
			
		||||
  @include dark();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (prefers-color-scheme: dark) {
 | 
			
		||||
  :root {
 | 
			
		||||
    @include dark();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,10 @@ export function isIOS() {
 | 
			
		||||
  return /iphone|ipad|ipod/.test(userAgent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isMobileScreen() {
 | 
			
		||||
  return window.innerWidth <= 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function selectOrCopy(el: HTMLElement, content: string) {
 | 
			
		||||
  const currentSelection = window.getSelection();
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +76,7 @@ export function queryMeta(key: string, defaultValue?: string): string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let currentId: string;
 | 
			
		||||
export function getCurrentCommitId() {
 | 
			
		||||
export function getCurrentVersion() {
 | 
			
		||||
  if (currentId) {
 | 
			
		||||
    return currentId;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user