mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-06-11 05:23:09 +08:00
webui: fix white screen routing/base and add mobile responsive sidebar
This commit is contained in:
@@ -13,7 +13,7 @@ import Skills from './pages/Skills';
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename="/webui">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Dashboard />} />
|
<Route index element={<Dashboard />} />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Terminal, Globe } from 'lucide-react';
|
import { Terminal, Globe, Menu } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAppContext } from '../context/AppContext';
|
import { useAppContext } from '../context/AppContext';
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { isGatewayOnline } = useAppContext();
|
const { isGatewayOnline, setSidebarOpen } = useAppContext();
|
||||||
|
|
||||||
const toggleLang = () => {
|
const toggleLang = () => {
|
||||||
const nextLang = i18n.language === 'en' ? 'zh' : 'en';
|
const nextLang = i18n.language === 'en' ? 'zh' : 'en';
|
||||||
@@ -15,6 +15,9 @@ const Header: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<header className="h-16 border-b border-zinc-800 bg-zinc-900/50 flex items-center justify-between px-6 shrink-0 z-10">
|
<header className="h-16 border-b border-zinc-800 bg-zinc-900/50 flex items-center justify-between px-6 shrink-0 z-10">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<button className="md:hidden p-2 rounded-lg hover:bg-zinc-800 text-zinc-300" onClick={() => setSidebarOpen(true)}>
|
||||||
|
<Menu className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
<div className="w-9 h-9 rounded-xl bg-indigo-500 flex items-center justify-center shadow-lg shadow-indigo-500/20">
|
<div className="w-9 h-9 rounded-xl bg-indigo-500 flex items-center justify-center shadow-lg shadow-indigo-500/20">
|
||||||
<Terminal className="w-5 h-5 text-white" />
|
<Terminal className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,15 +3,20 @@ import { Outlet, useLocation } from 'react-router-dom';
|
|||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
|
import { useAppContext } from '../context/AppContext';
|
||||||
|
|
||||||
const Layout: React.FC = () => {
|
const Layout: React.FC = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { sidebarOpen, setSidebarOpen } = useAppContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen bg-zinc-950 text-zinc-50 overflow-hidden font-sans">
|
<div className="flex flex-col h-screen bg-zinc-950 text-zinc-50 overflow-hidden font-sans">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="flex flex-1 min-h-0">
|
<div className="flex flex-1 min-h-0">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
{sidebarOpen && (
|
||||||
|
<button className="fixed inset-0 top-16 bg-black/40 z-30 md:hidden" onClick={() => setSidebarOpen(false)} aria-label="close sidebar" />
|
||||||
|
)}
|
||||||
<main className="flex-1 flex flex-col min-w-0 relative bg-zinc-950/50">
|
<main className="flex-1 flex flex-col min-w-0 relative bg-zinc-950/50">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import NavItem from './NavItem';
|
|||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { token, setToken } = useAppContext();
|
const { token, setToken, sidebarOpen } = useAppContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="w-64 border-r border-zinc-800 bg-zinc-900/30 flex flex-col shrink-0">
|
<aside className={`fixed md:static inset-y-16 left-0 z-40 w-64 border-r border-zinc-800 bg-zinc-900/95 md:bg-zinc-900/30 flex flex-col shrink-0 transform transition-transform duration-200 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}>
|
||||||
<nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
|
<nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
|
||||||
<NavItem icon={<LayoutDashboard className="w-5 h-5" />} label={t('dashboard')} to="/" />
|
<NavItem icon={<LayoutDashboard className="w-5 h-5" />} label={t('dashboard')} to="/" />
|
||||||
<NavItem icon={<MessageSquare className="w-5 h-5" />} label={t('chat')} to="/chat" />
|
<NavItem icon={<MessageSquare className="w-5 h-5" />} label={t('chat')} to="/chat" />
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { CronJob, Cfg, Session, Skill } from '../types';
|
|||||||
|
|
||||||
interface AppContextType {
|
interface AppContextType {
|
||||||
token: string;
|
token: string;
|
||||||
|
sidebarOpen: boolean;
|
||||||
|
setSidebarOpen: (open: boolean) => void;
|
||||||
setToken: (token: string) => void;
|
setToken: (token: string) => void;
|
||||||
isGatewayOnline: boolean;
|
isGatewayOnline: boolean;
|
||||||
setIsGatewayOnline: (online: boolean) => void;
|
setIsGatewayOnline: (online: boolean) => void;
|
||||||
@@ -29,7 +31,16 @@ interface AppContextType {
|
|||||||
const AppContext = createContext<AppContextType | undefined>(undefined);
|
const AppContext = createContext<AppContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [token, setToken] = useState('cg_nLnov7DPd9yqZDYPEU5pHnoa');
|
const initialToken = (() => {
|
||||||
|
try {
|
||||||
|
const u = new URL(window.location.href);
|
||||||
|
return u.searchParams.get('token') || 'cg_nLnov7DPd9yqZDYPEU5pHnoa';
|
||||||
|
} catch {
|
||||||
|
return 'cg_nLnov7DPd9yqZDYPEU5pHnoa';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const [token, setToken] = useState(initialToken);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
const [isGatewayOnline, setIsGatewayOnline] = useState(true);
|
const [isGatewayOnline, setIsGatewayOnline] = useState(true);
|
||||||
const [cfg, setCfg] = useState<Cfg>({});
|
const [cfg, setCfg] = useState<Cfg>({});
|
||||||
const [cfgRaw, setCfgRaw] = useState('{}');
|
const [cfgRaw, setCfgRaw] = useState('{}');
|
||||||
@@ -109,7 +120,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={{
|
<AppContext.Provider value={{
|
||||||
token, setToken, isGatewayOnline, setIsGatewayOnline,
|
token, setToken, sidebarOpen, setSidebarOpen, isGatewayOnline, setIsGatewayOnline,
|
||||||
cfg, setCfg, cfgRaw, setCfgRaw, nodes, setNodes,
|
cfg, setCfg, cfgRaw, setCfgRaw, nodes, setNodes,
|
||||||
cron, setCron, skills, setSkills,
|
cron, setCron, skills, setSkills,
|
||||||
sessions, setSessions,
|
sessions, setSessions,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {defineConfig, loadEnv} from 'vite';
|
|||||||
export default defineConfig(({mode}) => {
|
export default defineConfig(({mode}) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
return {
|
return {
|
||||||
|
base: '/webui/',
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
define: {
|
define: {
|
||||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
|
|||||||
Reference in New Issue
Block a user