import React, { useEffect, useRef, useMemo, useState, Fragment } from "react"; import Image from 'next/image'; import styles from "./home.module.scss"; import newStyles from './sidebar.module.scss'; import DragIcon from "../icons/drag.svg"; import logoSrc from "../icons/logo.png"; import deepSeekSrc from "../icons/deepSeek.png"; import { AppstoreOutlined, EditOutlined, MenuOutlined, HomeOutlined, PlusOutlined, StarOutlined, CommentOutlined } from '@ant-design/icons'; import * as AllIcons from '@ant-design/icons'; import { useAppConfig, useChatStore, useGlobalStore } from "../store"; import { DEFAULT_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH, NARROW_SIDEBAR_WIDTH, } from "../constant"; import { useLocation, useNavigate } from "react-router-dom"; import { isIOS, useMobileScreen, getContrastColor } from "../utils"; import api from "@/app/api/api"; import { Button, Drawer, Dropdown, Empty, Form, Input, Menu, message, Modal, Rate, Tag, Select } from "antd"; import { downloadFile } from "../utils/index"; import dayjs from "dayjs"; import type { DrawerProps, RadioChangeEvent } from 'antd'; import '@/app/styles/common.scss' const FormItem = Form.Item; import { processSliceData } from "@/app/utils/index"; export function useHotKey() { const chatStore = useChatStore(); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.altKey || e.ctrlKey) { if (e.key === "ArrowUp") { chatStore.nextSession(-1); } else if (e.key === "ArrowDown") { chatStore.nextSession(1); } } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }); } export function useDragSideBar() { const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); const config = useAppConfig(); const startX = useRef(0); const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH); const lastUpdateTime = useRef(Date.now()); const toggleSideBar = () => { config.update((config) => { if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) { config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH; } else { config.sidebarWidth = NARROW_SIDEBAR_WIDTH; } }); }; const onDragStart = (e: MouseEvent) => { // Remembers the initial width each time the mouse is pressed startX.current = e.clientX; startDragWidth.current = config.sidebarWidth; const dragStartTime = Date.now(); const handleDragMove = (e: MouseEvent) => { if (Date.now() < lastUpdateTime.current + 20) { return; } lastUpdateTime.current = Date.now(); const d = e.clientX - startX.current; const nextWidth = limit(startDragWidth.current + d); config.update((config) => { if (nextWidth < MIN_SIDEBAR_WIDTH) { config.sidebarWidth = NARROW_SIDEBAR_WIDTH; } else { config.sidebarWidth = nextWidth; } }); }; const handleDragEnd = () => { // In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth window.removeEventListener("pointermove", handleDragMove); window.removeEventListener("pointerup", handleDragEnd); // if user click the drag icon, should toggle the sidebar const shouldFireClick = Date.now() - dragStartTime < 300; if (shouldFireClick) { toggleSideBar(); } }; window.addEventListener("pointermove", handleDragMove); window.addEventListener("pointerup", handleDragEnd); }; const isMobileScreen = useMobileScreen(); const shouldNarrow = !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH; useEffect(() => { const barWidth = shouldNarrow ? NARROW_SIDEBAR_WIDTH : limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH); const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`; document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); }, [config.sidebarWidth, isMobileScreen, shouldNarrow]); return { onDragStart, shouldNarrow, }; } export function SideBarContainer(props: { children: React.ReactNode; onDragStart: (e: MouseEvent) => void; shouldNarrow: boolean; className?: string; }) { const isMobileScreen = useMobileScreen(); const isIOSMobile = useMemo( () => isIOS() && isMobileScreen, [isMobileScreen], ); const { children, className, onDragStart, shouldNarrow } = props; // shadow-sidebar return (
{children}
onDragStart(e as any)} >
); } // Sidebar 头部 export function SideBarHeader(props: { title?: string | React.ReactNode; subTitle?: string | React.ReactNode; logo?: React.ReactNode; children?: React.ReactNode; }) { const { title, subTitle, logo, children } = props; const navigate = useNavigate(); return (
{ window.open('http://10.1.14.17:3200/appCenter') }} >
{logo}
{title}
{subTitle}
{children}
); } export function SideBarBody(props: { children: React.ReactNode; onClick?: (e: React.MouseEvent) => void; }) { const { onClick, children } = props; return (
{children}
); } export function SideBarTail(props: { primaryAction?: React.ReactNode; secondaryAction?: React.ReactNode; }) { const { primaryAction, secondaryAction } = props; return (
{primaryAction}
{secondaryAction}
); } interface AppDrawerProps { isMobileScreen: boolean, selectedAppId: string, type: 'all' | 'collect', open: boolean, onClose: () => void, } export const SideBar = (props: { className?: string }) => { // useHotKey(); const { onDragStart, shouldNarrow } = useDragSideBar(); const [showPluginSelector, setShowPluginSelector] = useState(false); const navigate = useNavigate(); const location = useLocation(); const chatStore = useChatStore(); const globalStore = useGlobalStore(); const [menuList, setMenuList] = useState([]) const [modalOpen, setModalOpen] = useState(false) const [form] = Form.useForm(); const getType = (): 'bigModel' | 'deepSeek' => { if (['/knowledgeChat', '/newChat'].includes(location.pathname)) { return 'bigModel'; } else if (['/deepseekChat', '/newDeepseekChat','/welcome'].includes(location.pathname)) { return 'deepSeek'; } else { return 'bigModel'; } } // 获取应用类型 app_type const fetchAppType = async () => { try { const res = await api.get(`/deepseek/api/app_type`); // 解析返回并设置状态 if (res && res.data) { setAppTypes([{ dictLabel: '收藏', dictValue: '收藏' }, ...res.data]); } } catch (error) { console.error('Failed to fetch app types:', error); } } // 获取应用列表 const fetchGetApplicationList = async (typeId?: string | null, name?: string) => { setAppListLoading(true); try { const data = { pageSize: 1000, pageNum: 1, userId: 1, isCollect: typeId === '收藏' ? '1' : null, typeId: typeId === '收藏' ? null : typeId, name: name, } const res: any = await api.post('/deepseek/api/getApplicationList', data); // 解析返回并设置状态 if (res && res.rows) { setAppListState(res.rows); if (name) { setSearchOptions(res.rows.map((item: any) => ({ label: item.name, value: item.appId }))); } } } catch (error) { console.error('Failed to fetch app list:', error); } finally { setAppListLoading(false); setSearchFetching(false); } } // 应用类型与列表状态 const [appTypes, setAppTypes] = useState([]); const [appListState, setAppListState] = useState([]); const [appListLoading, setAppListLoading] = useState(false); const [openKeys, setOpenKeys] = useState([]); // 当前打开的菜单项 // 渲染应用列表为 Antd Menu(一级:类型,带图标;二级:该类型下的应用) const previewAppList = () => { const iconFor = (index: number) => { const icons = [, , ]; return icons[index % icons.length]; }; const items = appTypes && appTypes.length ? appTypes.map((t: any, idx: number) => { // 当该一级是当前打开项时,使用 appListState 作为 children(fetchGetApplicationList 填充) const typeKey = `${t.dictValue}`; let children = [] as any[]; // if (openKeys.includes(typeKey)) { if (appListLoading) { children = [{ key: `${typeKey}-loading`, label: 加载中... }]; } else { children = (appListState || []).map((a: any, i: number) => ({ key: a.appId || `app-${idx}-${i}`, // label: a.dictLabel || a.name || a.appName || `应用 ${i}`, label: a.iconColor ? (() => { const C = (AllIcons as any)[a.iconType]; const iconColor = getContrastColor(a.iconColor); return C ?

{a.name || a.appName || `应用 ${i}`}
: {a.iconType} })() : {a.name || a.appName || `应用 ${i}`} , onClick: () => { chatStore.updateCurrentSession((value) => { value.appId = a.appId; }); // 点击二级应用的处理:打印或导航(保留为 UI 先) chatStore.clearSessions(); if (getType() === 'bigModel') { globalStore.setSelectedAppId(a.appId); } else { const search = `?showMenu=false&chatMode=LOCAL&appId=${a.appId}`; navigate({ pathname: '/knowledgeChat', search: search, }) globalStore.setSelectedAppId(a.appId); // location.reload(); } } })); } // } if (openKeys.includes(typeKey)) { return { key: t.dictValue, icon: iconFor(idx), label: t.dictLabel || `类型 ${idx}`, children: children.length ? children : [{ key: `empty-${idx}`, label: 暂无应用, }], }; } else { return { key: t.dictValue, icon: iconFor(idx), label: t.dictLabel || `类型 ${idx}`, children: [], }; } }) : []; return ( { console.log('e', e); if (e.length > 0) { setOpenKeys(e.slice(-1)); fetchGetApplicationList(e.slice(-1)[0]); } else { setOpenKeys([]); } }} /> ); } // 获取聊天列表 const fetchChatList = async (chatMode?: 'ONLINE' | 'LOCAL') => { try { let url = ''; const appId = globalStore.selectedAppId; if (appId) { url = `/deepseek/api/dialog/list/${appId}`; const res = await api.get(url); const list = res.data.map((item: any) => { return { ...item, children: item.children.map((child: any) => { const items = [ { key: '1', label: ( { e.preventDefault(); e.stopPropagation(); setModalOpen(true); form.setFieldsValue({ dialogId: child.key, dialogName: child.label }); }}> 重命名 ), }, { key: '2', label: ( { try { let blob = null; if (getType() === 'bigModel') { if (chatMode === 'LOCAL') { blob = await api.post(`/deepseek/api/dialog/export/${child.key}`, {}, { responseType: 'blob' }); } else { blob = await api.post(`/bigmodel/api/dialog/export/${child.key}`, {}, { responseType: 'blob' }); } } else { blob = await api.post(`/bigmodel/api/dialog/export/${child.key}`, {}, { responseType: 'blob' }); } const fileName = `${child.label}.xlsx`; downloadFile(blob, fileName); } catch (error) { console.error(error); } }}> 导出 ), }, { key: '3', label: ( { try { if (getType() === 'bigModel') { if (chatMode === 'LOCAL') { await api.delete(`/deepseek/api/dialog/del/${child.key}`); await fetchChatList(chatMode); } else { await api.delete(`/bigmodel/api/dialog/del/${child.key}`); await fetchChatList(); } } else { await api.delete(`/bigmodel/api/dialog/del/${child.key}`); await fetchChatList(); } chatStore.clearSessions(); useChatStore.setState({ message: { content: '', role: 'assistant', } }); } catch (error) { console.error(error); } }}> 删除 ), }, ]; return { ...child, label:
{child.label}
e.stopPropagation()} />
} }) } }) setMenuList(list); } } catch (error) { console.error(error) } } useEffect(() => { // if (getType() === 'bigModel') { if (globalStore.selectedAppId) { fetchChatList('LOCAL'); } // } }, [globalStore.selectedAppId]); useEffect(() => { fetchAppType(); chatStore.clearSessions(); useChatStore.setState({ message: { content: '', role: 'assistant', } }); }, []); useEffect(() => { fetchChatList(chatStore.chatMode); }, [chatStore.chatMode]); const isMobileScreen = useMobileScreen(); const [drawerOpen, setDrawerOpen] = useState(false); const [drawerType, setDrawerType] = useState<'all' | 'collect'>('all'); // Select 远程搜索 UI 状态(UI-only 模拟) const [searchOptions, setSearchOptions] = useState([]); const [searchFetching, setSearchFetching] = useState(false); const handleSearch = (value: string) => { console.log('search value', value); if (!value) { setSearchOptions([]); return; } setSearchFetching(true); fetchGetApplicationList(null, value); // 模拟异步请求 }; const [placement, setPlacement] = useState('left'); return ( <> { isMobileScreen ? globalStore.showMenu && { console.log('close drawer'); e.preventDefault(); e.stopPropagation(); globalStore.setShowMenu(false); }} open={globalStore.showMenu} key={placement} style={{ // 1. 自定义 Drawer 整体背景色(包括头部、内容区) background: 'none', // 浅灰背景,可替换为 #fff、rgb(255,255,255) 等 width: '100%', }} styles={{ mask: { zIndex: 1000, // 遮罩层层级(原 maskStyle 中的配置) background: 'rgba(0, 0, 0, 0.3)', // 遮罩层背景色/透明度 // 其他遮罩层样式均可在此配置,与原 maskStyle 用法一致 }, }} > {/* { getType() === 'deepSeek' &&
} */} {/* { isMobileScreen &&
} */} {/* 盈科 */} : '' } // logo={getType() === 'bigModel' || true ? : ''} >
{/* 搜索框 - antd Select 远程搜索(UI only) */}
: globalStore.showMenu && {/* { getType() === 'deepSeek' &&
} */} { isMobileScreen &&
} {/* 盈科2 */}
: '' } // logo={getType() === 'bigModel' || true ? : ''} >
{/* 搜索框 - antd Select 远程搜索(UI only) */}
} ); }