mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 22:10:12 +09:00
use lazy loading and admin pages (in dev)
This commit is contained in:
parent
d5f917191e
commit
5c08d04b89
10
app/src/assets/admin/all.less
Normal file
10
app/src/assets/admin/all.less
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
@import "menu";
|
||||||
|
|
||||||
|
.admin-page {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100vh - 56px);
|
||||||
|
height: max-content;
|
||||||
|
}
|
59
app/src/assets/admin/menu.less
Normal file
59
app/src/assets/admin/menu.less
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.admin-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: var(--background-sidebar);
|
||||||
|
transition: 0.225s ease-in-out;
|
||||||
|
min-height: calc(100vh - 56px);
|
||||||
|
transition-property: width, background, box-shadow, opacity;
|
||||||
|
border-right: 0;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&.close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
width: 260px;
|
||||||
|
border-right: 1px solid hsl(var(--border));
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: calc(100% - 1.5rem);
|
||||||
|
height: max-content;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--conversation-card);
|
||||||
|
margin: 0.75rem 0.75rem -0.25rem;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease-in-out;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--conversation-card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--conversation-card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,7 +78,7 @@ function Markdown({ children, className }: MarkdownProps) {
|
|||||||
code({ inline, className, children, ...props }) {
|
code({ inline, className, children, ...props }) {
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
const language = match ? match[1] : "";
|
const language = match ? match[1] : "";
|
||||||
if (language) return parseFile(children.toString());
|
if (language === "file") return parseFile(children.toString());
|
||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<div className={`markdown-syntax`}>
|
<div className={`markdown-syntax`}>
|
||||||
<div className={`markdown-syntax-header`}>
|
<div className={`markdown-syntax-header`}>
|
||||||
|
50
app/src/components/admin/MenuBar.tsx
Normal file
50
app/src/components/admin/MenuBar.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {selectMenu} from "@/store/menu.ts";
|
||||||
|
import React, {useEffect, useMemo, useState} from "react";
|
||||||
|
import {LayoutDashboard, Settings} from "lucide-react";
|
||||||
|
import router from "@/router.tsx";
|
||||||
|
import {useLocation} from "react-router-dom";
|
||||||
|
|
||||||
|
type MenuItemProps = {
|
||||||
|
title: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MenuItem({ title, icon, path }: MenuItemProps) {
|
||||||
|
const location = useLocation();
|
||||||
|
const active = useMemo(() => (
|
||||||
|
location.pathname === `/admin${path}` || (location.pathname + "/") === `/admin${path}`
|
||||||
|
), [location.pathname, path]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`menu-item ${active ? "active" : ""}`}
|
||||||
|
onClick={() => router.navigate(`/admin${path}`)}
|
||||||
|
>
|
||||||
|
<div className={`menu-item-icon`}>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
<div className={`menu-item-title`}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MenuBar() {
|
||||||
|
const open = useSelector(selectMenu);
|
||||||
|
const [close, setClose] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) setClose(false);
|
||||||
|
else setTimeout(() => setClose(true), 200);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`admin-menu ${open ? "open" : ""} ${close ? "close" : ""}`}>
|
||||||
|
<MenuItem title={"Dashboard"} icon={<LayoutDashboard />} path={"/"} />
|
||||||
|
<MenuItem title={"Dashboard"} icon={<Settings />} path={"/config"} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuBar;
|
@ -15,12 +15,12 @@ import { useToast } from "@/components/ui/use-toast.ts";
|
|||||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||||
import { alignSelector, contextSelector } from "@/store/settings.ts";
|
import { alignSelector, contextSelector } from "@/store/settings.ts";
|
||||||
import { FileArray } from "@/conversation/file.ts";
|
import { FileArray } from "@/conversation/file.ts";
|
||||||
import WebToggle from "@/components/home/components/WebToggle.tsx";
|
import WebToggle from "@/components/home/assemblies/WebToggle.tsx";
|
||||||
import ChatSpace from "@/components/home/ChatSpace.tsx";
|
import ChatSpace from "@/components/home/ChatSpace.tsx";
|
||||||
import ChatFooter from "@/components/home/ChatFooter.tsx";
|
import ChatFooter from "@/components/home/ChatFooter.tsx";
|
||||||
import SendButton from "@/components/home/components/SendButton.tsx";
|
import SendButton from "@/components/home/assemblies/SendButton.tsx";
|
||||||
import ChatInput from "@/components/home/components/ChatInput.tsx";
|
import ChatInput from "@/components/home/assemblies/ChatInput.tsx";
|
||||||
import ScrollAction from "@/components/home/components/ScrollAction.tsx";
|
import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx";
|
||||||
|
|
||||||
function ChatWrapper() {
|
function ChatWrapper() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -127,7 +127,7 @@ function ChatWrapper() {
|
|||||||
<FileProvider value={files} onChange={setFiles} />
|
<FileProvider value={files} onChange={setFiles} />
|
||||||
<ChatInput
|
<ChatInput
|
||||||
className={align ? "align" : ""}
|
className={align ? "align" : ""}
|
||||||
ref={target}
|
target={target}
|
||||||
value={input}
|
value={input}
|
||||||
onValueChange={setInput}
|
onValueChange={setInput}
|
||||||
onEnterPressed={async () => await handleSend(auth, model, web)}
|
onEnterPressed={async () => await handleSend(auth, model, web)}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "@/store";
|
|
||||||
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
||||||
import { selectCurrent, selectHistory } from "@/store/chat.ts";
|
import { selectCurrent, selectHistory } from "@/store/chat.ts";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
@ -17,7 +16,7 @@ import {
|
|||||||
updateConversationList,
|
updateConversationList,
|
||||||
} from "@/conversation/history.ts";
|
} from "@/conversation/history.ts";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { setMenu } from "@/store/menu.ts";
|
import {selectMenu, setMenu} from "@/store/menu.ts";
|
||||||
import {
|
import {
|
||||||
Copy,
|
Copy,
|
||||||
Eraser,
|
Eraser,
|
||||||
@ -340,7 +339,7 @@ function SidebarMenu() {
|
|||||||
function SideBar() {
|
function SideBar() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const open = useSelector((state: RootState) => state.menu.open);
|
const open = useSelector(selectMenu);
|
||||||
const auth = useSelector(selectAuthenticated);
|
const auth = useSelector(selectAuthenticated);
|
||||||
const [operateConversation, setOperateConversation] = useState<Operation>({
|
const [operateConversation, setOperateConversation] = useState<Operation>({
|
||||||
target: null,
|
target: null,
|
||||||
|
@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
type ChatInputProps = {
|
type ChatInputProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
ref?: React.RefObject<HTMLInputElement>;
|
target?: React.RefObject<HTMLInputElement>;
|
||||||
value: string;
|
value: string;
|
||||||
onValueChange: (value: string) => void;
|
onValueChange: (value: string) => void;
|
||||||
onEnterPressed: () => void;
|
onEnterPressed: () => void;
|
||||||
@ -13,7 +13,7 @@ type ChatInputProps = {
|
|||||||
|
|
||||||
function ChatInput({
|
function ChatInput({
|
||||||
className,
|
className,
|
||||||
ref,
|
target,
|
||||||
value,
|
value,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
onEnterPressed,
|
onEnterPressed,
|
||||||
@ -24,7 +24,7 @@ function ChatInput({
|
|||||||
<Input
|
<Input
|
||||||
id={`input`}
|
id={`input`}
|
||||||
className={`input-box ${className || ""}`}
|
className={`input-box ${className || ""}`}
|
||||||
ref={ref}
|
ref={target}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onValueChange(e.target.value);
|
onValueChange(e.target.value);
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from "@/utils/env.ts";
|
} from "@/utils/env.ts";
|
||||||
import { getMemory } from "@/utils/memory.ts";
|
import { getMemory } from "@/utils/memory.ts";
|
||||||
|
|
||||||
export const version = "3.6.13rc";
|
export const version = "3.6.13rc1";
|
||||||
export const dev: boolean = getDev();
|
export const dev: boolean = getDev();
|
||||||
export const deploy: boolean = true;
|
export const deploy: boolean = true;
|
||||||
export let rest_api: string = getRestApi(deploy);
|
export let rest_api: string = getRestApi(deploy);
|
||||||
|
@ -2,9 +2,12 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|||||||
import Home from "./routes/Home.tsx";
|
import Home from "./routes/Home.tsx";
|
||||||
import NotFound from "./routes/NotFound.tsx";
|
import NotFound from "./routes/NotFound.tsx";
|
||||||
import Auth from "./routes/Auth.tsx";
|
import Auth from "./routes/Auth.tsx";
|
||||||
import Generation from "./routes/Generation.tsx";
|
import { lazy, Suspense } from "react";
|
||||||
import Sharing from "./routes/Sharing.tsx";
|
|
||||||
import Article from "@/routes/Article.tsx";
|
const Generation = lazy(() => import("@/routes/Generation.tsx"));
|
||||||
|
const Sharing = lazy(() => import("@/routes/Sharing.tsx"));
|
||||||
|
const Article = lazy(() => import("@/routes/Article.tsx"));
|
||||||
|
const Admin = lazy(() => import("@/routes/Admin.tsx"));
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -22,17 +25,43 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
id: "generation",
|
id: "generation",
|
||||||
path: "/generate",
|
path: "/generate",
|
||||||
Component: Generation,
|
element: (
|
||||||
|
<Suspense>
|
||||||
|
<Generation />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
ErrorBoundary: NotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "share",
|
id: "share",
|
||||||
path: "/share/:hash",
|
path: "/share/:hash",
|
||||||
Component: Sharing,
|
element: (
|
||||||
|
<Suspense>
|
||||||
|
<Sharing />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
ErrorBoundary: NotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "article",
|
id: "article",
|
||||||
path: "/article",
|
path: "/article",
|
||||||
Component: Article,
|
element: (
|
||||||
|
<Suspense>
|
||||||
|
<Article />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
ErrorBoundary: NotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "admin",
|
||||||
|
path: "/admin",
|
||||||
|
element: (
|
||||||
|
<Suspense>
|
||||||
|
<Admin />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
children: [],
|
||||||
|
ErrorBoundary: NotFound,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
12
app/src/routes/Admin.tsx
Normal file
12
app/src/routes/Admin.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import "@/assets/admin/all.less";
|
||||||
|
import MenuBar from "@/components/admin/MenuBar.tsx";
|
||||||
|
|
||||||
|
function Admin() {
|
||||||
|
return (
|
||||||
|
<div className={`admin-page`}>
|
||||||
|
<MenuBar />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Admin;
|
@ -24,3 +24,5 @@ export const menuSlice = createSlice({
|
|||||||
|
|
||||||
export const { toggleMenu, closeMenu, openMenu, setMenu } = menuSlice.actions;
|
export const { toggleMenu, closeMenu, openMenu, setMenu } = menuSlice.actions;
|
||||||
export default menuSlice.reducer;
|
export default menuSlice.reducer;
|
||||||
|
|
||||||
|
export const selectMenu = (state: any) => state.menu.open;
|
||||||
|
Loading…
Reference in New Issue
Block a user