feat: chat panel redesigned ui

This commit is contained in:
butterfly
2024-04-16 14:07:51 +08:00
parent 3fc9b91bf1
commit 51a1d9f92a
41 changed files with 1350 additions and 526 deletions

View File

@@ -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,
};
}

View 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
View 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
}, []);
}

View File

@@ -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;
}

View 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]);
}

View 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);
};
}, []);
}