diff --git a/app/src/assets/admin/all.less b/app/src/assets/admin/all.less new file mode 100644 index 0000000..06f2337 --- /dev/null +++ b/app/src/assets/admin/all.less @@ -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; +} diff --git a/app/src/assets/admin/menu.less b/app/src/assets/admin/menu.less new file mode 100644 index 0000000..059ff9d --- /dev/null +++ b/app/src/assets/admin/menu.less @@ -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; + } + } +} diff --git a/app/src/components/Markdown.tsx b/app/src/components/Markdown.tsx index c83371d..362415f 100644 --- a/app/src/components/Markdown.tsx +++ b/app/src/components/Markdown.tsx @@ -78,7 +78,7 @@ function Markdown({ children, className }: MarkdownProps) { code({ inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ""); const language = match ? match[1] : ""; - if (language) return parseFile(children.toString()); + if (language === "file") return parseFile(children.toString()); return !inline && match ? (
diff --git a/app/src/components/admin/MenuBar.tsx b/app/src/components/admin/MenuBar.tsx new file mode 100644 index 0000000..12dac19 --- /dev/null +++ b/app/src/components/admin/MenuBar.tsx @@ -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 ( +
router.navigate(`/admin${path}`)} + > +
+ {icon} +
+
+ {title} +
+
+ ) +} + +function MenuBar() { + const open = useSelector(selectMenu); + const [close, setClose] = useState(false); + useEffect(() => { + if (open) setClose(false); + else setTimeout(() => setClose(true), 200); + }, [open]); + + return ( +
+ } path={"/"} /> + } path={"/config"} /> +
+ ) +} + +export default MenuBar; diff --git a/app/src/components/home/ChatWrapper.tsx b/app/src/components/home/ChatWrapper.tsx index 0f6cec4..8417184 100644 --- a/app/src/components/home/ChatWrapper.tsx +++ b/app/src/components/home/ChatWrapper.tsx @@ -15,12 +15,12 @@ import { useToast } from "@/components/ui/use-toast.ts"; import { ToastAction } from "@/components/ui/toast.tsx"; import { alignSelector, contextSelector } from "@/store/settings.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 ChatFooter from "@/components/home/ChatFooter.tsx"; -import SendButton from "@/components/home/components/SendButton.tsx"; -import ChatInput from "@/components/home/components/ChatInput.tsx"; -import ScrollAction from "@/components/home/components/ScrollAction.tsx"; +import SendButton from "@/components/home/assemblies/SendButton.tsx"; +import ChatInput from "@/components/home/assemblies/ChatInput.tsx"; +import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx"; function ChatWrapper() { const { t } = useTranslation(); @@ -127,7 +127,7 @@ function ChatWrapper() { await handleSend(auth, model, web)} diff --git a/app/src/components/home/SideBar.tsx b/app/src/components/home/SideBar.tsx index f1d8397..d26a228 100644 --- a/app/src/components/home/SideBar.tsx +++ b/app/src/components/home/SideBar.tsx @@ -1,6 +1,5 @@ import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; -import { RootState } from "@/store"; import { selectAuthenticated, selectUsername } from "@/store/auth.ts"; import { selectCurrent, selectHistory } from "@/store/chat.ts"; import { useRef, useState } from "react"; @@ -17,7 +16,7 @@ import { updateConversationList, } from "@/conversation/history.ts"; import { Button } from "@/components/ui/button.tsx"; -import { setMenu } from "@/store/menu.ts"; +import {selectMenu, setMenu} from "@/store/menu.ts"; import { Copy, Eraser, @@ -340,7 +339,7 @@ function SidebarMenu() { function SideBar() { const { t } = useTranslation(); const dispatch = useDispatch(); - const open = useSelector((state: RootState) => state.menu.open); + const open = useSelector(selectMenu); const auth = useSelector(selectAuthenticated); const [operateConversation, setOperateConversation] = useState({ target: null, diff --git a/app/src/components/home/components/ChatInput.tsx b/app/src/components/home/assemblies/ChatInput.tsx similarity index 92% rename from app/src/components/home/components/ChatInput.tsx rename to app/src/components/home/assemblies/ChatInput.tsx index f349bf6..20c0762 100644 --- a/app/src/components/home/components/ChatInput.tsx +++ b/app/src/components/home/assemblies/ChatInput.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; type ChatInputProps = { className?: string; - ref?: React.RefObject; + target?: React.RefObject; value: string; onValueChange: (value: string) => void; onEnterPressed: () => void; @@ -13,7 +13,7 @@ type ChatInputProps = { function ChatInput({ className, - ref, + target, value, onValueChange, onEnterPressed, @@ -24,7 +24,7 @@ function ChatInput({ ) => { onValueChange(e.target.value); diff --git a/app/src/components/home/components/ScrollAction.tsx b/app/src/components/home/assemblies/ScrollAction.tsx similarity index 100% rename from app/src/components/home/components/ScrollAction.tsx rename to app/src/components/home/assemblies/ScrollAction.tsx diff --git a/app/src/components/home/components/SendButton.tsx b/app/src/components/home/assemblies/SendButton.tsx similarity index 100% rename from app/src/components/home/components/SendButton.tsx rename to app/src/components/home/assemblies/SendButton.tsx diff --git a/app/src/components/home/components/WebToggle.tsx b/app/src/components/home/assemblies/WebToggle.tsx similarity index 100% rename from app/src/components/home/components/WebToggle.tsx rename to app/src/components/home/assemblies/WebToggle.tsx diff --git a/app/src/conf.ts b/app/src/conf.ts index 58c2e7b..f831127 100644 --- a/app/src/conf.ts +++ b/app/src/conf.ts @@ -8,7 +8,7 @@ import { } from "@/utils/env.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 deploy: boolean = true; export let rest_api: string = getRestApi(deploy); diff --git a/app/src/router.tsx b/app/src/router.tsx index ddc0096..5ccf419 100644 --- a/app/src/router.tsx +++ b/app/src/router.tsx @@ -2,9 +2,12 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import Home from "./routes/Home.tsx"; import NotFound from "./routes/NotFound.tsx"; import Auth from "./routes/Auth.tsx"; -import Generation from "./routes/Generation.tsx"; -import Sharing from "./routes/Sharing.tsx"; -import Article from "@/routes/Article.tsx"; +import { lazy, Suspense } from "react"; + +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([ { @@ -22,17 +25,43 @@ const router = createBrowserRouter([ { id: "generation", path: "/generate", - Component: Generation, + element: ( + + + + ), + ErrorBoundary: NotFound, }, { id: "share", path: "/share/:hash", - Component: Sharing, + element: ( + + + + ), + ErrorBoundary: NotFound, }, { id: "article", path: "/article", - Component: Article, + element: ( + +
+ + ), + ErrorBoundary: NotFound, + }, + { + id: "admin", + path: "/admin", + element: ( + + + + ), + children: [], + ErrorBoundary: NotFound, }, ]); diff --git a/app/src/routes/Admin.tsx b/app/src/routes/Admin.tsx new file mode 100644 index 0000000..015553f --- /dev/null +++ b/app/src/routes/Admin.tsx @@ -0,0 +1,12 @@ +import "@/assets/admin/all.less"; +import MenuBar from "@/components/admin/MenuBar.tsx"; + +function Admin() { + return ( +
+ +
+ ) +} + +export default Admin; diff --git a/app/src/store/menu.ts b/app/src/store/menu.ts index 9f3aac4..365d04f 100644 --- a/app/src/store/menu.ts +++ b/app/src/store/menu.ts @@ -24,3 +24,5 @@ export const menuSlice = createSlice({ export const { toggleMenu, closeMenu, openMenu, setMenu } = menuSlice.actions; export default menuSlice.reducer; + +export const selectMenu = (state: any) => state.menu.open;