feat: seperate chat page

This commit is contained in:
butterfly
2024-04-12 10:57:57 +08:00
parent 67acc38a1f
commit 0a8e5d6734
56 changed files with 3868 additions and 25 deletions

View File

@@ -0,0 +1,24 @@
.search {
display: flex;
max-width: 460px;
height: 50px;
padding: 16px;
align-items: center;
gap: 8px;
flex-shrink: 0;
border-radius: 16px;
border: 1px solid var(--Light-Text-Black, #18182A);
background: var(--light-opacity-white-70, rgba(255, 255, 255, 0.70));
box-shadow: 0px 8px 40px 0px rgba(60, 68, 255, 0.12);
.icon {
height: 20px;
width: 20px;
flex: 0 0;
}
.input {
height: 18px;
flex: 1 1;
}
}

View File

@@ -0,0 +1,30 @@
import styles from "./index.module.scss";
import SearchIcon from "@/app/icons/search.svg";
export interface SearchProps {
value?: string;
onSearch?: (v: string) => void;
placeholder?: string;
}
const Search = (props: SearchProps) => {
const { placeholder = "", value, onSearch } = props;
return (
<div className={styles["search"]}>
<div className={styles["icon"]}>
<SearchIcon />
</div>
<input
className={styles["input"]}
placeholder={placeholder}
value={value}
onChange={(e) => {
e.preventDefault();
onSearch?.(e.target.value);
}}
/>
</div>
);
};
export default Search;

View File

@@ -0,0 +1,70 @@
import { isValidElement } from "react";
type IconMap = {
active: JSX.Element;
inactive: JSX.Element;
};
interface Action {
id: string;
icons: JSX.Element | IconMap;
className?: string;
}
export interface TabActionsProps {
actionsShema: Action[];
onSelect: (id: string) => void;
selected: string;
groups: string[][];
className?: string;
}
export default function TabActions(props: TabActionsProps) {
const { actionsShema, onSelect, selected, groups, className } = props;
const content = groups.reduce((res, group, ind, arr) => {
res.push(
...group.map((i) => {
const action = actionsShema.find((a) => a.id === i);
if (!action) {
return <></>;
}
const { icons } = action;
let activeIcon, inactiveIcon;
if (isValidElement(icons)) {
activeIcon = icons;
inactiveIcon = icons;
} else {
activeIcon = (icons as IconMap).active;
inactiveIcon = (icons as IconMap).inactive;
}
return (
<div
key={action.id}
className={` ${
selected === action.id ? "bg-blue-900" : "bg-transparent"
} p-3 rounded-md items-center ${action.className}`}
onClick={(e) => {
e.preventDefault();
if (selected !== action.id) {
onSelect?.(action.id);
}
}}
>
{selected === action.id ? activeIcon : inactiveIcon}
</div>
);
}),
);
if (ind < arr.length - 1) {
res.push(<div className=" flex-1"></div>);
}
return res;
}, [] as JSX.Element[]);
return (
<div className={`flex flex-col items-center ${className}`}>{content}</div>
);
}

View File

@@ -9,7 +9,7 @@ import styles from "./home.module.scss";
import BotIcon from "../icons/bot.svg";
import LoadingIcon from "../icons/three-dots.svg";
import { getCSSVar, useMobileScreen } from "../utils";
import { getCSSVar } from "../utils";
import dynamic from "next/dynamic";
import { ModelProvider, Path, SlotID } from "../constant";
@@ -23,13 +23,15 @@ import {
Route,
useLocation,
} from "react-router-dom";
import { SideBar } from "./sidebar";
import { SideBar } from "@/app/containers/Sidebar";
import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client";
import { ClientApi } from "../client/api";
import { useAccessStore } from "../store";
import { identifyDefaultClaudeModel } from "../utils/checkers";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import backgroundUrl from "!url-loader!@/app/icons/background.svg";
export function Loading(props: { noLogo?: boolean }) {
return (
@@ -44,7 +46,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
loading: () => <Loading noLogo />,
});
const Chat = dynamic(async () => (await import("./chat")).Chat, {
const Chat = dynamic(async () => await import("@/app/containers/Chat"), {
loading: () => <Loading noLogo />,
});
@@ -129,8 +131,6 @@ function Screen() {
const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth;
const isMobileScreen = useMobileScreen();
const shouldTightBorder =
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
useEffect(() => {
loadAsyncGoogleFont();
@@ -140,10 +140,11 @@ function Screen() {
<div
className={
styles.container +
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
`${styles["container"]} ${styles["tight-container"]} ${
getLang() === "ar" ? styles["rtl-screen"] : ""
}`
}
style={{ background: `url(${backgroundUrl})` }}
>
{isAuth ? (
<>
@@ -153,14 +154,19 @@ function Screen() {
<>
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
<div className={styles["window-content"]} id={SlotID.AppBody}>
<Routes>
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
<div
className={`flex flex-col h-[100%] w-[--window-content-width`}
id={SlotID.AppBody}
>
<ErrorBoundary>
<Routes>
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</ErrorBoundary>
</div>
</>
)}