diff --git a/package.json b/package.json index bafee40..1592b14 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,11 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.2.0", + "@mui/lab": "^7.0.0-beta.14", + "@mui/material": "^7.2.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/public/index.html b/public/index.html index aa069f2..626b3c2 100644 --- a/public/index.html +++ b/public/index.html @@ -4,40 +4,26 @@ - + - - - React App + + + + + + + + + + ThinkML - Material Design
- diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..b9c15ea --- /dev/null +++ b/src/App.css @@ -0,0 +1,30 @@ +/* ==================== App 全局样式 ==================== */ + +/* 基础盒模型设置 */ +* { + box-sizing: border-box; +} + +/* Loading spinner 通用样式 */ +.loading-spinner { + border: 2px solid rgba(0, 0, 0, 0.1); + border-top: 2px solid #1976d2; + border-radius: 50%; + width: 20px; + height: 20px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* 平滑的主题过渡效果 */ +.theme-transition * { + transition: + background-color 0.3s ease, + color 0.3s ease, + border-color 0.3s ease, + box-shadow 0.3s ease; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index dc255e1..ec26e37 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,48 +1,70 @@ import React from 'react'; -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import MainLayout from './MainLayout/MainLayout'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import ErrorBoundary from './components/ErrorBoundary'; +import { MuiThemeProvider } from './contexts/MuiThemeContext'; + +// Layout +import MainLayout from './MainLayout'; + +// Pages import Chat from './Pages/Chat'; import Gpts from './Pages/Gpts'; +import GptsDetail from './Pages/Gpts/Detail'; +import GptsNew from './Pages/Gpts/New'; import Login from './Pages/Login'; import Signup from './Pages/Signup'; import Settings from './Pages/Settings'; +import SettingsPreferences from './Pages/Settings/Preferences'; +import SettingsNotifications from './Pages/Settings/Notifications'; +import SettingsKeyboard from './Pages/Settings/Keyboard'; +import SettingsDataControls from './Pages/Settings/DataControls'; +import SettingsPlan from './Pages/Settings/Plan'; +import Privacy from './Pages/Privacy'; +import Terms from './Pages/Terms'; +import SimpleTest from './Pages/SimpleTest'; import NotFound from './Pages/NotFound'; -import Profile from './Pages/Settings/Profile'; -import Preferences from './Pages/Settings/Preferences'; -import Notifications from './Pages/Settings/Notifications'; -import PrivacySecurity from './Pages/Settings/PrivacySecurity'; -import DataManagement from './Pages/Settings/DataManagement'; -import { ThemeInitializer } from './components/ThemeInitializer'; -function App() { +const App: React.FC = () => { return ( - <> - - - - {/* Public routes */} - } /> - } /> - {/* Protected routes - require login */} - }> - } /> - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* 404 page */} - } /> - - - + + + + + + {/* Public Routes */} + } /> + } /> + } /> + } /> + } /> + + {/* Main App Routes with Layout */} + }> + } /> + } /> + } /> + } /> + } /> + + {/* Settings Routes */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* 404 Route */} + } /> + + + + + ); -} +}; export default App; diff --git a/src/MainLayout/MainContent/MainContent.module.css b/src/MainLayout/MainContent/MainContent.module.css deleted file mode 100644 index ed4c679..0000000 --- a/src/MainLayout/MainContent/MainContent.module.css +++ /dev/null @@ -1,90 +0,0 @@ -.gptMainContent { - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - height: calc(100vh - 56px); - padding-top: 15vh; - background: #fff; - z-index: 1; -} -.gptWelcomeBig { - font-size: 2.4rem; - font-weight: 500; - color: #222; - margin-bottom: 36px; - text-align: center; -} -.gptCard { - width: 900px; - max-width: 96vw; - background: #fff; - border-radius: 36px; - box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.08); - padding: 36px 40px 24px 40px; - display: flex; - flex-direction: column; - align-items: stretch; -} -.gptCardInputPlaceholder { - color: #999; - font-size: 1.6rem; - font-weight: 400; - margin-bottom: 48px; - margin-left: 2px; -} -.gptCardToolbar { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 12px; -} -.gptToolbarLeft { - display: flex; - align-items: center; - gap: 12px; -} -.gptToolbarRight { - display: flex; - align-items: center; - gap: 18px; -} -.gptToolbarIcon { - font-size: 1.6rem; - color: #222; - background: none; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; -} -.gptToolbarMicBg { - background: #f2f2f2; - width: 40px; - height: 40px; - font-size: 1.7rem; - display: flex; - align-items: center; - justify-content: center; -} -.gptToolbarText { - font-size: 1.1rem; - color: #222; - margin-left: 2px; -} -.thinkmlPurple { - font-size: 2.6rem; - font-weight: 700; - color: #a259ff; - letter-spacing: 0.5px; - margin-bottom: 4px; -} -.gptSubtitle { - font-size: 1.25rem; - color: #7a7a8c; - font-weight: 400; - margin-bottom: 8px; - text-align: center; -} diff --git a/src/MainLayout/MainContent/MainContent.tsx b/src/MainLayout/MainContent/MainContent.tsx index 23e9672..cb6aa13 100644 --- a/src/MainLayout/MainContent/MainContent.tsx +++ b/src/MainLayout/MainContent/MainContent.tsx @@ -1,54 +1,159 @@ import React from 'react'; -import styles from './MainContent.module.css'; -import { Plus, Menu, Wrench, Mic, LayoutGrid } from 'lucide-react'; +import { Box, Container, Typography, Paper, Stack, IconButton, Chip, Avatar } from '@mui/material'; +import { + Add as PlusIcon, + Menu as MenuIcon, + Build as WrenchIcon, + Mic as MicIcon, + Apps as AppsIcon, + AutoAwesome as SparklesIcon, +} from '@mui/icons-material'; const MainContent: React.FC = () => { return ( -
- {/* ThinkML logo and subtitle */} -
+ {/* ThinkML Logo and Title */} + + + + + + + ThinkML + + + + Your AI Assistant + + + + {/* Input Card */} + theme.shadows[8], + }, + transition: 'all 0.3s ease', + }} > - - - - -
ThinkML
-
Your AI Assistant
-
- -
-
Ask anything
-
-
- - - - tools -
-
- - + + {/* Toolbar */} + + {/* Left Toolbar */} + + + + + + + + + + + -
-
-
-
+ + + {/* Right Toolbar */} + + + + + + + + + + + + {/* Helper Text */} + + Start a conversation with your AI assistant + + ); }; diff --git a/src/MainLayout/MainLayout.module.css b/src/MainLayout/MainLayout.module.css deleted file mode 100644 index ea76236..0000000 --- a/src/MainLayout/MainLayout.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.container { - display: flex; - width: 100%; - height: 100vh; - overflow: hidden; - background: #f7f7f8; -} - -.main { - flex: 1; - display: flex; - flex-direction: column; - background: #ffffff; - border-left: 1px solid #e5e5e5; -} - -.content { - flex: 1; - overflow: hidden; - position: relative; -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .container { - background: #343541; - } - - .main { - background: #444654; - border-left: 1px solid #565869; - } -} diff --git a/src/MainLayout/MainLayout.tsx b/src/MainLayout/MainLayout.tsx index 553186c..9d4174a 100644 --- a/src/MainLayout/MainLayout.tsx +++ b/src/MainLayout/MainLayout.tsx @@ -1,20 +1,42 @@ import React from 'react'; import { Outlet } from 'react-router-dom'; +import { Box, CssBaseline } from '@mui/material'; import Sidebar from './Sidebar'; -import styles from './MainLayout.module.css'; import NavBar from './NavBar/NavBar'; const MainLayout: React.FC = () => { return ( -
+ + + + {/* Sidebar */} -
+ + {/* Main Content Area */} + + {/* Navigation Bar */} -
+ + {/* Page Content */} + -
-
-
+ + + ); }; diff --git a/src/MainLayout/NavBar/NavBar.module.css b/src/MainLayout/NavBar/NavBar.module.css deleted file mode 100644 index cba0c10..0000000 --- a/src/MainLayout/NavBar/NavBar.module.css +++ /dev/null @@ -1,280 +0,0 @@ -.navbar { - height: 60px; - background: #ffffff; - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid #e5e5e5; - position: relative; - font-weight: 600; - font-size: 20px; - padding: 0 24px; - box-sizing: border-box; - width: 100%; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.left { - display: flex; - align-items: center; - gap: 8px; -} - -.title { - font-size: 24px; - font-weight: 700; - color: #202123; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.version { - font-weight: 500; - font-size: 16px; - color: #6b7280; - background: #f3f4f6; - padding: 4px 8px; - border-radius: 12px; - border: 1px solid #e5e7eb; -} - -.right { - display: flex; - align-items: center; - gap: 16px; - height: 100%; -} - -.bubble { - margin-right: 8px; - color: #6b7280; - cursor: pointer; - transition: color 0.2s ease; - padding: 8px; - border-radius: 8px; - position: relative; -} - -.bubble:hover { - color: #3b82f6; - background: #f3f4f6; -} - -.notificationBadge { - position: absolute; - top: 4px; - right: 4px; - width: 8px; - height: 8px; - background: #ef4444; - border-radius: 50%; - border: 2px solid #ffffff; -} - -.avatar { - width: 40px; - height: 40px; - border-radius: 50%; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - display: flex; - align-items: center; - justify-content: center; - color: #fff; - font-weight: 700; - font-size: 16px; - cursor: pointer; - transition: - transform 0.2s ease, - box-shadow 0.2s ease; - border: 2px solid #ffffff; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.avatar:hover { - transform: scale(1.05); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -.userMenu { - position: relative; -} - -.userDropdown { - position: absolute; - top: 100%; - right: 0; - background: #ffffff; - border: 1px solid #e5e5e5; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - min-width: 200px; - z-index: 1000; - margin-top: 8px; - max-height: 400px; - overflow-y: auto; -} - -.dropdownHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px; - border-bottom: 1px solid #f3f4f6; -} - -.dropdownHeader h3 { - margin: 0; - font-size: 16px; - font-weight: 600; - color: #374151; -} - -.closeButton { - background: none; - border: none; - cursor: pointer; - color: #6b7280; - padding: 4px; - border-radius: 4px; - transition: background-color 0.2s ease; -} - -.closeButton:hover { - background: #f3f4f6; -} - -.dropdownItem { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 16px; - color: #374151; - text-decoration: none; - font-size: 14px; - transition: background-color 0.2s ease; - border-bottom: 1px solid #f3f4f6; - cursor: pointer; -} - -.dropdownItem:last-child { - border-bottom: none; -} - -.dropdownItem:hover { - background: #f9fafb; -} - -.dropdownItem.danger { - color: #dc2626; -} - -.dropdownItem.danger:hover { - background: #fef2f2; -} - -.dropdownItem.unread { - background: #f0f9ff; - border-left: 3px solid #3b82f6; -} - -.notificationContent { - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -.notificationTitle { - font-weight: 600; - color: #374151; - font-size: 14px; -} - -.notificationMessage { - color: #6b7280; - font-size: 13px; - line-height: 1.4; -} - -.notificationTime { - color: #9ca3af; - font-size: 12px; -} - -.emptyNotification { - text-align: center; - color: #6b7280; - font-style: italic; - padding: 20px; -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .navbar { - background: #343541; - border-bottom: 1px solid #565869; - } - - .title { - color: #ffffff; - } - - .version { - background: #4b5563; - color: #d1d5db; - border-color: #6b7280; - } - - .bubble { - color: #d1d5db; - } - - .bubble:hover { - background: #4b5563; - } - - .userDropdown { - background: #374151; - border-color: #565869; - } - - .dropdownHeader { - border-bottom-color: #4b5563; - } - - .dropdownHeader h3 { - color: #d1d5db; - } - - .dropdownItem { - color: #d1d5db; - border-bottom-color: #4b5563; - } - - .dropdownItem:hover { - background: #4b5563; - } - - .dropdownItem.unread { - background: #1e3a8a; - border-left-color: #3b82f6; - } - - .notificationTitle { - color: #d1d5db; - } - - .notificationMessage { - color: #9ca3af; - } - - .notificationTime { - color: #6b7280; - } - - .emptyNotification { - color: #9ca3af; - } -} diff --git a/src/MainLayout/NavBar/NavBar.tsx b/src/MainLayout/NavBar/NavBar.tsx index 545a495..1d765e0 100644 --- a/src/MainLayout/NavBar/NavBar.tsx +++ b/src/MainLayout/NavBar/NavBar.tsx @@ -1,139 +1,337 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import styles from './NavBar.module.css'; -import { Settings, LogOut, User, Bell, HelpCircle, Sun, Moon, X } from 'lucide-react'; -import { useUserStore } from '../../stores/userStore'; +import React, { useState } from 'react'; +import { + AppBar, + Toolbar, + Typography, + IconButton, + Badge, + Avatar, + Menu, + MenuItem, + Box, + Divider, + ListItemIcon, + ListItemText, + Chip, +} from '@mui/material'; +import { + Notifications as NotificationsIcon, + Settings as SettingsIcon, + Logout as LogoutIcon, + Person as PersonIcon, + Brightness4 as DarkModeIcon, + Brightness7 as LightModeIcon, + BrightnessAuto as AutoModeIcon, + Close as CloseIcon, +} from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +import { useMuiTheme } from '../../contexts/MuiThemeContext'; const NavBar: React.FC = () => { - const [showUserMenu, setShowUserMenu] = useState(false); - const [showNotifications, setShowNotifications] = useState(false); - const userMenuRef = useRef(null); - const notificationRef = useRef(null); - const { toggleDarkMode, isDarkMode } = useUserStore(); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) { - setShowUserMenu(false); - } - if (notificationRef.current && !notificationRef.current.contains(event.target as Node)) { - setShowNotifications(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); - - const handleThemeToggle = () => { - toggleDarkMode(); - }; + const navigate = useNavigate(); + const { themeMode, setThemeMode } = useMuiTheme(); + + const [userMenuAnchor, setUserMenuAnchor] = useState(null); + const [notificationMenuAnchor, setNotificationMenuAnchor] = useState(null); + + const isUserMenuOpen = Boolean(userMenuAnchor); + const isNotificationMenuOpen = Boolean(notificationMenuAnchor); - const handleLogout = () => { - // Add logout logic here - // TODO: Implement logout logic + // Implement theme toggle logic + const toggleTheme = () => { + const modes: ('light' | 'dark' | 'auto')[] = ['light', 'dark', 'auto']; + const currentIndex = modes.indexOf(themeMode); + const nextIndex = (currentIndex + 1) % modes.length; + setThemeMode(modes[nextIndex]); }; + // Mock notification data const notifications = [ { id: 1, - title: 'New GPT available', - message: 'Code Assistant GPT is now available', - time: '2 minutes ago', + title: 'New Message', + message: 'You have a new chat message', + time: '5 minutes ago', unread: true, }, { id: 2, - title: 'Chat history updated', - message: 'Your recent conversations have been saved', + title: 'System Update', + message: 'Material-UI theme system is enabled', time: '1 hour ago', + unread: true, + }, + { + id: 3, + title: 'Welcome', + message: 'Welcome to ThinkML platform', + time: '3 hours ago', unread: false, }, ]; + const unreadCount = notifications.filter((n) => n.unread).length; + + const handleUserMenuOpen = (event: React.MouseEvent) => { + setUserMenuAnchor(event.currentTarget); + }; + + const handleUserMenuClose = () => { + setUserMenuAnchor(null); + }; + + const handleNotificationMenuOpen = (event: React.MouseEvent) => { + setNotificationMenuAnchor(event.currentTarget); + }; + + const handleNotificationMenuClose = () => { + setNotificationMenuAnchor(null); + }; + + const handleThemeChange = (mode: 'light' | 'dark' | 'auto') => { + setThemeMode(mode); + }; + + const getThemeIcon = () => { + switch (themeMode) { + case 'light': + return ; + case 'dark': + return ; + case 'auto': + return ; + default: + return ; + } + }; + + const getThemeText = () => { + switch (themeMode) { + case 'light': + return 'Light Mode'; + case 'dark': + return 'Dark Mode'; + case 'auto': + return 'Auto Mode'; + default: + return 'Auto Mode'; + } + }; + return ( -
- {/* left */} -
- ThinkML - 4o -
- - {/* right */} -
- {/* Notifications */} -
-
setShowNotifications(!showNotifications)}> - - {notifications.some((n) => n.unread) &&
} -
- - {showNotifications && ( -
-
-

Notifications

- -
- {notifications.map((notification) => ( -
-
-
{notification.title}
-
{notification.message}
-
{notification.time}
-
-
- ))} - {notifications.length === 0 && ( -
-
No notifications
-
- )} -
- )} -
+ + + {/* Left: Brand */} + + navigate('/')} + > + ThinkML + + + - {/* Theme Toggle */} -
- {isDarkMode ? : } -
+ {/* Right: Actions */} + + {/* Theme Toggle */} + + {getThemeIcon()} + - {/* Help */} -
- -
+ {/* Notifications */} + + + + + + + {/* User Avatar */} + + + U + + +
{/* User Menu */} -
-
setShowUserMenu(!showUserMenu)}> - JD -
- - {showUserMenu && ( -
- - - Profile - - - - Settings - -
- - Sign Out -
-
+ + navigate('/settings')}> + + + + Personal Profile + + + navigate('/settings')}> + + + + Settings + + + + + {/* Theme Options */} + handleThemeChange('light')}> + + + + Light Mode + {themeMode === 'light' && } + + + handleThemeChange('dark')}> + + + + Dark Mode + {themeMode === 'dark' && } + + + handleThemeChange('auto')}> + + + + Auto Mode + {themeMode === 'auto' && } + + + + + navigate('/login')}> + + + + Logout + + + + {/* Notifications Menu */} + + {/* Header */} + + + Notifications + + + + + + + {/* Notifications List */} + {notifications.length > 0 ? ( + notifications.map((notification) => ( + + + + {notification.title} + + + {notification.time} + + + + {notification.message} + + + )) + ) : ( + + + No notifications + + )} -
-
-
+ + + ); }; diff --git a/src/MainLayout/Sidebar/Sidebar.module.css b/src/MainLayout/Sidebar/Sidebar.module.css deleted file mode 100644 index dd72f8d..0000000 --- a/src/MainLayout/Sidebar/Sidebar.module.css +++ /dev/null @@ -1,351 +0,0 @@ -.gptSidebar { - width: 260px; - min-width: 260px; - transition: width 0.3s ease; - background: #202123; - display: flex; - flex-direction: column; - height: 100vh; - position: relative; - color: #ffffff; -} - -.collapsed { - width: 60px !important; - min-width: 60px !important; - transition: width 0.3s ease; -} - -.collapseBtn { - background: none; - border: none; - cursor: pointer; - margin-left: 8px; - padding: 4px; - display: flex; - align-items: center; -} - -.gptSidebarTop { - padding: 20px 16px 0 16px; -} - -.gptSidebarLogoRow { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -} - -.gptSidebarLogo { - font-size: 2.1rem; - color: #ffffff; - transition: color 0.2s ease; -} - -.gptSidebarMenu { - font-size: 1.5rem; - color: #a1a1aa; - cursor: pointer; - min-width: 22px; - z-index: 2; - background: none; - border: none; - padding: 8px; - display: flex; - align-items: center; - transition: all 0.2s ease; - border-radius: 6px; -} - -.gptSidebarMenu:hover { - color: #ffffff; - background: #343541; -} - -.collapsed .gptSidebarMenu { - color: #ffffff; - background: #343541; - border-radius: 6px; -} - -.gptSidebarSection { - margin-top: 20px; - padding: 0 16px; -} - -.gptSidebarMainbtns .gptSidebarBtn { - display: flex; - align-items: center; - gap: 12px; - font-size: 1rem; - color: #ffffff; - padding: 12px 16px; - cursor: pointer; - border-radius: 8px; - transition: all 0.2s ease; - text-decoration: none; - background: none; - border: none; - width: 100%; - text-align: left; - margin-bottom: 4px; - border: 1px solid transparent; -} - -.gptSidebarMainbtns .gptSidebarBtn:hover { - background: #343541; - border-color: #565869; -} - -.gptSidebarMainbtns .gptSidebarBtn.active { - background: #343541; - border-color: #565869; - color: #ffffff; -} - -.gptSidebarGroup { - display: flex; - align-items: center; - gap: 12px; - font-size: 0.95rem; - color: #a1a1aa; - padding: 12px 16px; - border-radius: 8px; - cursor: pointer; - transition: all 0.2s ease; - text-decoration: none; - background: none; - border: none; - width: 100%; - text-align: left; - margin-bottom: 4px; - border: 1px solid transparent; -} - -.gptSidebarGroup:hover { - background: #343541; - border-color: #565869; - color: #ffffff; -} - -.gptSidebarGroup.active { - background: #343541; - border-color: #565869; - color: #ffffff; -} - -.gptSidebarProject .gptSidebarBtn { - color: #10a37f; - font-weight: 500; -} - -.gptSidebarHistoryTitle { - font-size: 0.9rem; - color: #a1a1aa; - margin-bottom: 8px; - margin-top: 12px; - padding: 0 16px; - font-weight: 500; -} - -.gptSidebarHistoryList { - max-height: 220px; - overflow-y: auto; - padding-right: 4px; -} - -.historyItem { - display: flex; - align-items: center; - gap: 12px; - font-size: 0.9rem; - color: #a1a1aa; - padding: 8px 16px; - border-radius: 6px; - cursor: pointer; - transition: all 0.2s ease; - margin-bottom: 2px; - border: 1px solid transparent; -} - -.historyItem:hover { - background: #343541; - border-color: #565869; - color: #ffffff; -} - -.historyIcon { - font-size: 1rem; - opacity: 0.8; -} - -.historyText { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.gptSidebarHistorySection { - position: static; - background: #202123; - padding-bottom: 8px; - z-index: 10; - max-height: 32vh; - overflow-y: auto; - border-top: 1px solid #343541; - margin-bottom: 40px; -} - -.gptSidebarBottom { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 16px; - border-top: 1px solid #343541; - background: #202123; - text-align: left; -} - -.gptSidebarPlan { - font-size: 0.9rem; - color: #10a37f; - font-weight: 500; - line-height: 1.4; - margin-left: 0; -} - -.gptSidebarPlanDesc { - color: #a1a1aa; - font-size: 0.8rem; - font-weight: 400; - margin-top: 4px; -} - -.gptSidebarBtn, -.gptSidebarGroup { - display: flex; - align-items: center; - gap: 12px; -} - -.gptSidebarBtn svg, -.gptSidebarGroup svg { - min-width: 20px; - margin-right: 0; - display: block; - color: inherit; -} - -/* Scrollbar styling */ -.gptSidebarHistoryList::-webkit-scrollbar { - width: 4px; -} - -.gptSidebarHistoryList::-webkit-scrollbar-track { - background: transparent; -} - -.gptSidebarHistoryList::-webkit-scrollbar-thumb { - background: #565869; - border-radius: 2px; -} - -.gptSidebarHistoryList::-webkit-scrollbar-thumb:hover { - background: #6b7280; -} - -/* Responsive design */ -@media (max-width: 768px) { - .gptSidebar { - width: 100%; - min-width: 100%; - } - - .collapsed { - width: 60px !important; - min-width: 60px !important; - } -} - -.searchModalOverlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.25); - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; -} - -.searchModal { - background: #23232b; - border-radius: 14px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25); - width: 380px; - max-width: 90vw; - padding: 0 0 12px 0; - display: flex; - flex-direction: column; -} - -.searchModalHeader { - display: flex; - align-items: center; - gap: 8px; - padding: 18px 18px 10px 18px; - border-bottom: 1px solid #343541; -} - -.searchInputBox { - flex: 1; - background: #23232b; - border: none; - outline: none; - color: #fff; - font-size: 1rem; - padding: 8px 12px; - border-radius: 6px; -} - -.closeSearchBtn { - background: none; - border: none; - color: #a1a1aa; - cursor: pointer; - padding: 4px; - border-radius: 4px; - transition: background 0.15s; -} -.closeSearchBtn:hover { - background: #343541; -} - -.searchResultList { - max-height: 320px; - overflow-y: auto; - padding: 8px 0; -} - -.searchResultItem { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 18px; - color: #d1d5db; - cursor: pointer; - border-radius: 8px; - transition: background 0.15s; -} -.searchResultItem:hover { - background: #343541; -} - -.searchResultEmpty { - color: #888; - text-align: center; - padding: 32px 0; - font-size: 1rem; -} diff --git a/src/MainLayout/Sidebar/Sidebar.tsx b/src/MainLayout/Sidebar/Sidebar.tsx index 53ae71c..65a772c 100644 --- a/src/MainLayout/Sidebar/Sidebar.tsx +++ b/src/MainLayout/Sidebar/Sidebar.tsx @@ -1,15 +1,51 @@ import React, { useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import styles from './Sidebar.module.css'; -import { Menu, Sparkles, Plus, Search, Book, Code, Star, Brain, Folder, X } from 'lucide-react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + Box, + Drawer, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + IconButton, + Typography, + Divider, + TextField, + Dialog, + DialogTitle, + DialogContent, + InputAdornment, + Paper, + Stack, + Tooltip, +} from '@mui/material'; +import { + AutoAwesome as SparklesIcon, + Add as PlusIcon, + Search as SearchIcon, + MenuBook as BookIcon, + Code as CodeIcon, + Star as StarIcon, + Psychology as BrainIcon, + Folder as FolderIcon, + Close as CloseIcon, + Chat as ChatIcon, + ChevronLeft as ChevronLeftIcon, + ChevronRight as ChevronRightIcon, + Upgrade as UpgradeIcon, +} from '@mui/icons-material'; import { useChatStore } from '../../stores/chatStore'; +const DRAWER_WIDTH = 280; +const COLLAPSED_WIDTH = 64; + const Sidebar: React.FC = () => { const [collapsed, setCollapsed] = useState(false); - const [hovered, setHovered] = useState(false); const [showSearch, setShowSearch] = useState(false); const [searchValue, setSearchValue] = useState(''); const location = useLocation(); + const navigate = useNavigate(); const { sessions, currentSessionId, newSession, switchSession } = useChatStore(); const handleSoraClick = () => { @@ -20,6 +56,11 @@ const Sidebar: React.FC = () => { window.open('https://chatgpt.com/codex/onboarding', '_blank', 'noopener,noreferrer'); }; + const handleNewSession = () => { + newSession(); + navigate('/chat'); + }; + // Search filter const filteredSessions = sessions.filter((session) => { if (!searchValue.trim()) return true; @@ -28,172 +69,346 @@ const Sidebar: React.FC = () => { ); }); - return ( - + + ); + + return ( + <> + {/* Sidebar Drawer */} + + {drawerContent} + + + {/* Search Dialog */} + setShowSearch(false)} maxWidth="sm" fullWidth> + + + Search Chat History + + setShowSearch(false)} size="small"> + + + + + + setSearchValue(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ mb: 2 }} + autoFocus + /> + + + {filteredSessions.length === 0 ? ( + + + + {searchValue ? 'No matching chats found' : 'Start typing to search'} + + + ) : ( + + {filteredSessions.map((session) => ( + + { + switchSession(session.id); + navigate('/chat'); + setShowSearch(false); + setSearchValue(''); + }} + sx={{ borderRadius: 1 }} + > + + + + + + + ))} + + )} + + + + ); }; diff --git a/src/Pages/Chat/Chat.module.css b/src/Pages/Chat/Chat.module.css deleted file mode 100644 index f4762f9..0000000 --- a/src/Pages/Chat/Chat.module.css +++ /dev/null @@ -1,440 +0,0 @@ -.chatContainer { - display: flex; - flex-direction: column; - height: 100vh; - background: #ffffff; -} - -.welcomeContainer { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - background: linear-gradient(135deg, #f7f7f8 0%, #ffffff 100%); -} - -.welcomeContent { - max-width: 600px; - width: 100%; - text-align: center; -} - -.welcomeHeader { - margin-bottom: 40px; -} - -.welcomeIconContainer { - display: flex; - justify-content: center; - margin-bottom: 20px; -} - -.welcomeIcon { - color: #10a37f; - background: linear-gradient(135deg, #10a37f 0%, #059669 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.welcomeTitle { - font-size: 2.5rem; - font-weight: 700; - color: #202123; - margin: 0 0 8px 0; - background: linear-gradient(135deg, #10a37f 0%, #059669 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.welcomeSubtitle { - font-size: 1.1rem; - color: #6b7280; - margin: 0; - font-weight: 400; -} - -.apiWarning { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - margin-top: 16px; - padding: 12px 16px; - background: #fef3c7; - border: 1px solid #f59e0b; - border-radius: 8px; - color: #92400e; - font-size: 0.875rem; - max-width: 500px; - margin-left: auto; - margin-right: auto; -} - -.apiWarning svg { - flex-shrink: 0; - color: #f59e0b; -} - -.chatInputContainer { - margin-bottom: 40px; -} - -.chatInputWrapper { - position: relative; - max-width: 500px; - margin: 0 auto; - background: #ffffff; - border: 1px solid #e5e7eb; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - transition: all 0.2s ease; -} - -.chatInputWrapper:focus-within { - border-color: #10a37f; - box-shadow: 0 4px 20px rgba(16, 163, 127, 0.15); -} - -.chatInput { - width: 100%; - min-height: 60px; - padding: 16px 60px 16px 20px; - border: none; - outline: none; - resize: none; - font-size: 1rem; - line-height: 1.5; - color: #374151; - background: transparent; - font-family: inherit; -} - -.chatInput::placeholder { - color: #9ca3af; -} - -.chatInputActions { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - display: flex; - gap: 4px; -} - -.actionButton { - background: none; - border: none; - padding: 8px; - border-radius: 6px; - color: #6b7280; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.actionButton:hover { - background: #f3f4f6; - color: #374151; -} - -.sendButton { - background: #10a37f; - border: none; - padding: 8px; - border-radius: 6px; - color: #ffffff; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.sendButton:hover:not(.disabled) { - background: #059669; - transform: scale(1.05); -} - -.sendButton.disabled { - background: #d1d5db; - cursor: not-allowed; -} - -.suggestions { - max-width: 500px; - margin: 0 auto; -} - -.suggestionsTitle { - font-size: 0.9rem; - color: #6b7280; - margin: 0 0 16px 0; - font-weight: 500; -} - -.suggestionButtons { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 12px; -} - -.suggestionButton { - background: #ffffff; - border: 1px solid #e5e7eb; - border-radius: 8px; - padding: 12px 16px; - color: #374151; - cursor: pointer; - transition: all 0.2s ease; - font-size: 0.9rem; - text-align: left; - line-height: 1.4; -} - -.suggestionButton:hover { - border-color: #10a37f; - background: #f0fdf4; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(16, 163, 127, 0.1); -} - -.chatHeader { - padding: 16px 24px; - border-bottom: 1px solid #e5e7eb; - background: #ffffff; -} - -.newChatButton { - background: #10a37f; - border: none; - padding: 8px 16px; - border-radius: 6px; - color: #ffffff; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9rem; - font-weight: 500; -} - -.newChatButton:hover { - background: #059669; - transform: translateY(-1px); -} - -.messagesContainer { - flex: 1; - overflow-y: auto; - padding: 20px 0; -} - -.message { - display: flex; - gap: 16px; - padding: 20px 24px; - border-bottom: 1px solid #f3f4f6; -} - -.message:last-child { - border-bottom: none; -} - -.userMessage { - background: #ffffff; -} - -.assistantMessage { - background: #f9fafb; -} - -.messageAvatar { - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.2rem; - flex-shrink: 0; -} - -.messageContent { - flex: 1; - max-width: calc(100% - 48px); -} - -.messageText { - color: #374151; - line-height: 1.6; - margin-bottom: 8px; - white-space: pre-wrap; -} - -.messageTime { - font-size: 0.8rem; - color: #9ca3af; -} - -.typingIndicator { - display: flex; - gap: 4px; - align-items: center; -} - -.typingIndicator span { - width: 8px; - height: 8px; - border-radius: 50%; - background: #10a37f; - animation: typing 1.4s infinite ease-in-out; -} - -.typingIndicator span:nth-child(1) { - animation-delay: -0.32s; -} - -.typingIndicator span:nth-child(2) { - animation-delay: -0.16s; -} - -@keyframes typing { - 0%, - 80%, - 100% { - transform: scale(0.8); - opacity: 0.5; - } - 40% { - transform: scale(1); - opacity: 1; - } -} - -.inputContainer { - padding: 20px 24px; - border-top: 1px solid #e5e7eb; - background: #ffffff; -} - -.inputWrapper { - position: relative; - background: #ffffff; - border: 1px solid #e5e7eb; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: all 0.2s ease; -} - -.inputWrapper:focus-within { - border-color: #10a37f; - box-shadow: 0 4px 20px rgba(16, 163, 127, 0.15); -} - -.messageInput { - width: 100%; - min-height: 50px; - padding: 12px 60px 12px 16px; - border: none; - outline: none; - resize: none; - font-size: 1rem; - line-height: 1.5; - color: #374151; - background: transparent; - font-family: inherit; -} - -.messageInput::placeholder { - color: #9ca3af; -} - -.inputActions { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - display: flex; - gap: 4px; -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .chatContainer { - background: #343541; - } - - .welcomeContainer { - background: linear-gradient(135deg, #343541 0%, #444654 100%); - } - - .welcomeTitle { - color: #ffffff; - } - - .welcomeSubtitle { - color: #d1d5db; - } - - .chatInputWrapper { - background: #374151; - border-color: #565869; - } - - .chatInput { - color: #d1d5db; - } - - .chatInput::placeholder { - color: #9ca3af; - } - - .suggestionButton { - background: #374151; - border-color: #565869; - color: #d1d5db; - } - - .suggestionButton:hover { - background: #4b5563; - border-color: #10a37f; - } - - .chatHeader { - background: #343541; - border-bottom-color: #565869; - } - - .userMessage { - background: #343541; - } - - .assistantMessage { - background: #444654; - } - - .messageText { - color: #d1d5db; - } - - .inputContainer { - background: #343541; - border-top-color: #565869; - } - - .inputWrapper { - background: #374151; - border-color: #565869; - } - - .messageInput { - color: #d1d5db; - } -} diff --git a/src/Pages/Chat/index.tsx b/src/Pages/Chat/index.tsx index eac936f..2647bb9 100644 --- a/src/Pages/Chat/index.tsx +++ b/src/Pages/Chat/index.tsx @@ -1,6 +1,31 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Send, Mic, Paperclip, Smile, Sparkles, Plus, AlertCircle } from 'lucide-react'; -import styles from './Chat.module.css'; +import { + Box, + Paper, + Typography, + TextField, + IconButton, + Button, + Avatar, + Alert, + CircularProgress, + Container, + Stack, + Fade, + AppBar, + Toolbar, +} from '@mui/material'; +import { + Send as SendIcon, + Mic as MicIcon, + AttachFile as AttachFileIcon, + EmojiEmotions as EmojiIcon, + AutoAwesome as SparklesIcon, + Add as PlusIcon, + Warning as AlertIcon, + Person as PersonIcon, + SmartToy as BotIcon, +} from '@mui/icons-material'; import { useChatStore, useCurrentMessages, Message } from '../../stores/chatStore'; import { useUserStore } from '../../stores/userStore'; import openAIService from '../../services/openaiService'; @@ -8,7 +33,7 @@ import openAIService from '../../services/openaiService'; const Chat: React.FC = () => { const [inputValue, setInputValue] = useState(''); const messagesEndRef = useRef(null); - const inputRef = useRef(null); + const inputRef = useRef(null); const { addMessage, setLoading, isLoading } = useChatStore(); const messages = useCurrentMessages(); const { settings } = useUserStore(); @@ -178,173 +203,334 @@ const Chat: React.FC = () => { }, 100); }; + const handleSuggestionClick = (suggestion: string) => { + setInputValue(suggestion); + if (inputRef.current) { + inputRef.current.focus(); + } + }; + // check API configuration status const apiConfigStatus = openAIService.getConfigurationStatus(); + // Typing indicator component + const TypingIndicator = () => ( + + + + + + ); + return ( -
+ {!hasStartedChat ? ( - // Welcome interface - shown when starting a new chat -
-
-
-
- -
-

{t('chat.welcome.title')}

-

{t('chat.welcome.subtitle')}

- - {/* API configuration status hint */} + // Welcome interface + + + + + + + + + {t('chat.welcome.title')} + + + + {t('chat.welcome.subtitle')} + + + {/* API configuration status */} {!apiConfigStatus.configured && ( -
- - {apiConfigStatus.message} -
+ + + + {apiConfigStatus.message} + + )} -
+ + + + {/* Input Area */} + + setInputValue(e.target.value)} + onKeyPress={handleKeyPress} + placeholder={t('chat.placeholder')} + disabled={isLoading} + InputProps={{ + endAdornment: ( + + + + + + + + + + + + + + + ), + }} + /> + + + {/* Suggestions */} + + + + {t('chat.welcome.suggestions')} + + + {[ + t('chat.welcome.suggestion1'), + t('chat.welcome.suggestion2'), + t('chat.welcome.suggestion3'), + t('chat.welcome.suggestion4'), + ].map((suggestion, index) => ( + + ))} + + + + + ) : ( + // Chat interface + <> + {/* Chat Header */} + + + + + + + {/* Messages Container */} + + + + {messages.map((message) => ( + + + {message.role === 'user' ? : } + -
-
-