mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-07 16:47:03 +08:00
feat: chat panel redesigned ui
This commit is contained in:
@@ -2,14 +2,13 @@ import {
|
||||
DEFAULT_SIDEBAR_WIDTH,
|
||||
MAX_SIDEBAR_WIDTH,
|
||||
MIN_SIDEBAR_WIDTH,
|
||||
NARROW_SIDEBAR_WIDTH,
|
||||
} from "@/app/constant";
|
||||
import { useAppConfig } from "../store/config";
|
||||
import { useEffect, useRef } from "react";
|
||||
import useMobileScreen from "@/app/hooks/useMobileScreen";
|
||||
import { useRef } from "react";
|
||||
|
||||
export default function useDragSideBar() {
|
||||
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
||||
const limit = (x: number) =>
|
||||
Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, x));
|
||||
|
||||
const config = useAppConfig();
|
||||
const startX = useRef(0);
|
||||
@@ -18,11 +17,7 @@ export default function useDragSideBar() {
|
||||
|
||||
const toggleSideBar = () => {
|
||||
config.update((config) => {
|
||||
if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) {
|
||||
config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;
|
||||
} else {
|
||||
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
|
||||
}
|
||||
config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,12 +34,13 @@ export default function useDragSideBar() {
|
||||
lastUpdateTime.current = Date.now();
|
||||
const d = e.clientX - startX.current;
|
||||
const nextWidth = limit(startDragWidth.current + d);
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-width",
|
||||
`${nextWidth}px`,
|
||||
);
|
||||
config.update((config) => {
|
||||
if (nextWidth < MIN_SIDEBAR_WIDTH) {
|
||||
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
|
||||
} else {
|
||||
config.sidebarWidth = nextWidth;
|
||||
}
|
||||
config.sidebarWidth = nextWidth;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -64,20 +60,12 @@ export default function useDragSideBar() {
|
||||
window.addEventListener("pointerup", handleDragEnd);
|
||||
};
|
||||
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const shouldNarrow =
|
||||
!isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
|
||||
|
||||
useEffect(() => {
|
||||
const barWidth = shouldNarrow
|
||||
? NARROW_SIDEBAR_WIDTH
|
||||
: limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
|
||||
const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
|
||||
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
|
||||
}, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
|
||||
// useLayoutEffect(() => {
|
||||
// const barWidth = limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
|
||||
// document.documentElement.style.setProperty("--sidebar-width", `${barWidth}px`);
|
||||
// }, [config.sidebarWidth]);
|
||||
|
||||
return {
|
||||
onDragStart,
|
||||
shouldNarrow,
|
||||
};
|
||||
}
|
||||
|
61
app/hooks/useListenWinResize.ts
Normal file
61
app/hooks/useListenWinResize.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useWindowSize } from "@/app/hooks/useWindowSize";
|
||||
import {
|
||||
WINDOW_WIDTH_2XL,
|
||||
WINDOW_WIDTH_LG,
|
||||
WINDOW_WIDTH_MD,
|
||||
WINDOW_WIDTH_SM,
|
||||
WINDOW_WIDTH_XL,
|
||||
DEFAULT_SIDEBAR_WIDTH,
|
||||
MAX_SIDEBAR_WIDTH,
|
||||
MIN_SIDEBAR_WIDTH,
|
||||
} from "@/app/constant";
|
||||
import { useAppConfig } from "../store/config";
|
||||
import { useReducer, useState } from "react";
|
||||
|
||||
export const MOBILE_MAX_WIDTH = 768;
|
||||
|
||||
const widths = [
|
||||
WINDOW_WIDTH_2XL,
|
||||
WINDOW_WIDTH_XL,
|
||||
WINDOW_WIDTH_LG,
|
||||
WINDOW_WIDTH_MD,
|
||||
WINDOW_WIDTH_SM,
|
||||
];
|
||||
|
||||
export default function useListenWinResize() {
|
||||
const config = useAppConfig();
|
||||
|
||||
const [_, refresh] = useReducer((x) => x + 1, 0);
|
||||
|
||||
useWindowSize((size) => {
|
||||
let nextSidebar = config.sidebarWidth;
|
||||
if (!nextSidebar) {
|
||||
switch (widths.find((w) => w < size.width)) {
|
||||
case WINDOW_WIDTH_2XL:
|
||||
nextSidebar = MAX_SIDEBAR_WIDTH;
|
||||
break;
|
||||
case WINDOW_WIDTH_XL:
|
||||
case WINDOW_WIDTH_LG:
|
||||
nextSidebar = DEFAULT_SIDEBAR_WIDTH;
|
||||
break;
|
||||
case WINDOW_WIDTH_MD:
|
||||
case WINDOW_WIDTH_SM:
|
||||
default:
|
||||
nextSidebar = MIN_SIDEBAR_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
nextSidebar = Math.max(
|
||||
MIN_SIDEBAR_WIDTH,
|
||||
Math.min(MAX_SIDEBAR_WIDTH, nextSidebar),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-width",
|
||||
`${nextSidebar}px`,
|
||||
);
|
||||
config.update((config) => {
|
||||
config.sidebarWidth = nextSidebar;
|
||||
});
|
||||
refresh();
|
||||
});
|
||||
}
|
25
app/hooks/useLoadData.ts
Normal file
25
app/hooks/useLoadData.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAppConfig } from "@/app/store/config";
|
||||
import { ClientApi } from "@/app/client/api";
|
||||
import { ModelProvider } from "@/app/constant";
|
||||
import { identifyDefaultClaudeModel } from "@/app/utils/checkers";
|
||||
|
||||
export function useLoadData() {
|
||||
const config = useAppConfig();
|
||||
|
||||
var api: ClientApi;
|
||||
if (config.modelConfig.model.startsWith("gemini")) {
|
||||
api = new ClientApi(ModelProvider.GeminiPro);
|
||||
} else if (identifyDefaultClaudeModel(config.modelConfig.model)) {
|
||||
api = new ClientApi(ModelProvider.Claude);
|
||||
} else {
|
||||
api = new ClientApi(ModelProvider.GPT);
|
||||
}
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const models = await api.llm.models();
|
||||
config.mergeModels(models);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}
|
@@ -1,12 +1,16 @@
|
||||
import { useLayoutEffect } from "react";
|
||||
import { useWindowSize } from "../utils";
|
||||
import { useWindowSize } from "@/app/hooks/useWindowSize";
|
||||
import { useRef } from "react";
|
||||
|
||||
export const MOBILE_MAX_WIDTH = 600;
|
||||
export const MOBILE_MAX_WIDTH = 768;
|
||||
|
||||
export default function useMobileScreen() {
|
||||
const { width } = useWindowSize();
|
||||
const widthRef = useRef<number>(0);
|
||||
|
||||
const isMobile = width <= MOBILE_MAX_WIDTH;
|
||||
useWindowSize((size) => {
|
||||
widthRef.current = size.width;
|
||||
});
|
||||
|
||||
const isMobile = widthRef.current <= MOBILE_MAX_WIDTH;
|
||||
|
||||
return isMobile;
|
||||
}
|
||||
|
34
app/hooks/useSwitchTheme.ts
Normal file
34
app/hooks/useSwitchTheme.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAppConfig } from "@/app/store/config";
|
||||
import { getCSSVar } from "@/app/utils";
|
||||
|
||||
export function useSwitchTheme() {
|
||||
const config = useAppConfig();
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.remove("light");
|
||||
document.body.classList.remove("dark");
|
||||
|
||||
if (config.theme === "dark") {
|
||||
document.body.classList.add("dark");
|
||||
} else if (config.theme === "light") {
|
||||
document.body.classList.add("light");
|
||||
}
|
||||
|
||||
const metaDescriptionDark = document.querySelector(
|
||||
'meta[name="theme-color"][media*="dark"]',
|
||||
);
|
||||
const metaDescriptionLight = document.querySelector(
|
||||
'meta[name="theme-color"][media*="light"]',
|
||||
);
|
||||
|
||||
if (config.theme === "auto") {
|
||||
metaDescriptionDark?.setAttribute("content", "#151515");
|
||||
metaDescriptionLight?.setAttribute("content", "#fafafa");
|
||||
} else {
|
||||
const themeColor = getCSSVar("--theme-color");
|
||||
metaDescriptionDark?.setAttribute("content", themeColor);
|
||||
metaDescriptionLight?.setAttribute("content", themeColor);
|
||||
}
|
||||
}, [config.theme]);
|
||||
}
|
36
app/hooks/useWindowSize.ts
Normal file
36
app/hooks/useWindowSize.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useLayoutEffect, useMemo, useRef } from "react";
|
||||
|
||||
type Size = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export function useWindowSize(callback: (size: Size) => void) {
|
||||
const callbackRef = useRef<typeof callback>();
|
||||
const hascalled = useRef(false);
|
||||
|
||||
if (typeof window !== "undefined" && !hascalled.current) {
|
||||
callback({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
hascalled.current = true;
|
||||
}
|
||||
|
||||
callbackRef.current = callback;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const onResize = () => {
|
||||
callbackRef.current?.({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
}
|
Reference in New Issue
Block a user