import { ChatContext } from '@/app/chat-context'; import { delDialogue, getDialogueList } from '@/client/api/request'; import { apiInterceptors } from '@/client/api/tools/interceptors'; import { DarkSvg, ModelSvg, SunnySvg } from '@/components/icons'; import UserBar from '@/new-components/layout/UserBar'; import type { IChatDialogueSchema } from '@/types/chat'; import { STORAGE_LANG_KEY, STORAGE_THEME_KEY } from '@/utils/constants/index'; import Icon, { ApartmentOutlined, AppstoreOutlined, DeleteOutlined, EditOutlined, GlobalOutlined, LineChartOutlined, MenuFoldOutlined, MenuUnfoldOutlined, MessageOutlined, PlusOutlined, RightOutlined, } from '@ant-design/icons'; import { Popover, Skeleton, Tooltip, message } from 'antd'; import cls from 'classnames'; import moment from 'moment'; import 'moment/locale/zh-cn'; import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; type RouteItem = { key: string; name: string; iconSrc: string; activeIconSrc?: string; path: string; isActive?: boolean; }; function smallMenuItemStyle(active?: boolean) { return `flex items-center justify-center mx-auto rounded w-14 h-14 text-xl hover:bg-blue-50/50 dark:hover:bg-blue-900/10 transition-colors cursor-pointer ${ active ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400 shadow-sm' : '' }`; } function SidebarPictureIcon({ src, activeSrc, active, alt, size = 32, }: { src: string; activeSrc?: string; active?: boolean; alt: string; size?: number; }) { return ; } function SideBar() { const { isMenuExpand, setIsMenuExpand, mode, setMode } = useContext(ChatContext); const router = useRouter(); const { pathname } = router; const isSettingsActive = pathname.startsWith('/construct/app') || pathname.startsWith('/construct/flow') || pathname.startsWith('/construct/prompt') || pathname.startsWith('/construct/dbgpts') || pathname.startsWith('/construct/models') || pathname === '/models_evaluation'; const { t, i18n } = useTranslation(); const [logo, setLogo] = useState('/logo_zh_latest.png'); const [settingsOpen, setSettingsOpen] = useState(false); const [dialogueList, setDialogueList] = useState([]); const [loadingDialogues, setLoadingDialogues] = useState(false); const fetchDialogueList = useCallback(async () => { setLoadingDialogues(true); try { const [, data] = await apiInterceptors(getDialogueList()); if (data && Array.isArray(data)) { setDialogueList(data.filter(item => item.chat_mode === 'chat_react_agent')); } } catch (e) { console.error('Failed to fetch dialogue list', e); } finally { setLoadingDialogues(false); } }, []); const handleDeleteDialogue = useCallback(async (e: React.MouseEvent, convUid: string) => { e.stopPropagation(); e.preventDefault(); try { const [err] = await apiInterceptors(delDialogue(convUid)); if (!err) { setDialogueList(prev => prev.filter(d => d.conv_uid !== convUid)); message.success('已删除'); } } catch (error) { console.error('Failed to delete dialogue', error); } }, []); const formatRelativeTime = useCallback((dateStr?: string) => { if (!dateStr) return ''; const date = new Date(dateStr); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return '刚刚'; if (diffMins < 60) return `${diffMins}分钟前`; if (diffHours < 24) return `${diffHours}小时前`; if (diffDays < 7) return `${diffDays}天前`; return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }); }, []); const handleToggleMenu = useCallback(() => { setIsMenuExpand(!isMenuExpand); }, [isMenuExpand, setIsMenuExpand]); const handleToggleTheme = useCallback(() => { const theme = mode === 'light' ? 'dark' : 'light'; setMode(theme); localStorage.setItem(STORAGE_THEME_KEY, theme); }, [mode, setMode]); const handleChangeLang = useCallback(() => { const language = i18n.language === 'en' ? 'zh' : 'en'; i18n.changeLanguage(language); if (language === 'zh') moment.locale('zh-cn'); if (language === 'en') moment.locale('en'); localStorage.setItem(STORAGE_LANG_KEY, language); }, [i18n]); const functions = useMemo(() => { const items: RouteItem[] = [ { key: 'explore', name: t('explore'), isActive: pathname === '/', iconSrc: '/pictures/explore.png', activeIconSrc: '/pictures/explore_active.png', path: '/', }, { key: 'skills', name: t('skills'), isActive: pathname.startsWith('/construct/skills'), iconSrc: '/pictures/skills.svg', activeIconSrc: '/pictures/skills_active.svg', path: '/construct/skills', }, { key: 'datasources', name: t('datasources'), isActive: pathname.startsWith('/construct/database'), iconSrc: '/pictures/datasource.svg', activeIconSrc: '/pictures/datasource_active.svg', path: '/construct/database', }, { key: 'knowledge', name: t('knowledge'), isActive: pathname.startsWith('/construct/knowledge'), iconSrc: '/pictures/knowledge_sidebar.svg', activeIconSrc: '/pictures/knowledge_sidebar_active.svg', path: '/construct/knowledge', }, ]; return items; }, [t, pathname]); const settingsContent = ( {t('management')} { router.push('/construct/app'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/app'), }, )} > {t('app_management')} { router.push('/construct/models'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/models'), }, )} > {t('model_manage')} { router.push('/construct/flow'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/flow'), }, )} > {t('awel_workflow')} { router.push('/construct/prompt'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/prompt'), }, )} > {t('prompts')} { router.push('/construct/dbgpts'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname.startsWith('/construct/dbgpts'), }, )} > {t('dbgpts_community')} { router.push('/models_evaluation'); setSettingsOpen(false); }} className={cls( 'flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors', { 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400': pathname === '/models_evaluation', }, )} > {t('models_evaluation')} ); useEffect(() => { const language = i18n.language; if (language === 'zh') moment.locale('zh-cn'); if (language === 'en') moment.locale('en'); }, [i18n.language]); useEffect(() => { setLogo(mode === 'dark' ? '/logo_s_latest.png' : '/logo_zh_latest.png'); }, [mode]); useEffect(() => { fetchDialogueList(); }, [fetchDialogueList]); // ============ COLLAPSED SIDEBAR ============ if (!isMenuExpand) { return ( {functions.map(item => ( ))} {/* Settings icon */} ); } // ============ EXPANDED SIDEBAR ============ return ( {/* LOGO + Collapse Toggle */} {/* New Task Button */} {t('new_task')} {/* Functions */} {functions.map(item => ( {item.name} ))} {/* Settings */} {t('construct')} {/* All Tasks Section */} {t('all_tasks')} {loadingDialogues ? ( ) : dialogueList.length > 0 ? ( {dialogueList.map(conv => ( {typeof conv.user_input === 'string' ? conv.user_input.slice(0, 40) || 'New Conversation' : 'New Conversation'} {conv.gmt_created && ( {formatRelativeTime(conv.gmt_created)} )} handleDeleteDialogue(e, conv.conv_uid)} className='text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0 mt-1' /> ))} ) : ( {t('no_tasks')} )} {/* Bottom: UserBar + toggles */} {mode === 'dark' ? : } {isMenuExpand ? : } ); } export default SideBar;
{t('no_tasks')}