import React, { useEffect, useRef, useMemo, useState, Fragment } from "react"; import styles from "./home.module.scss"; import DragIcon from "../icons/drag.svg"; import faviconSrc from "../icons/favicon.png"; import deepSeekSrc from "../icons/deepSeek.png"; import { AppstoreOutlined, EditOutlined, MenuOutlined, HomeOutlined, PlusOutlined, StarOutlined } 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 } from "../utils"; import api from "@/app/api/api"; import { Button, Drawer, Dropdown, Empty, Form, Input, Menu, message, Modal, Rate, Tag } from "antd"; import { downloadFile } from "../utils/index"; import dayjs from "dayjs"; const FormItem = Form.Item; 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; return (
{children}
onDragStart(e as any)} >
); } export function SideBarHeader(props: { title?: string | React.ReactNode; subTitle?: string | React.ReactNode; logo?: React.ReactNode; children?: React.ReactNode; }) { const { title, subTitle, logo, children } = props; return (
{title}
{subTitle}
{logo}
{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, } const AppDrawer: React.FC = (props) => { const { isMobileScreen, selectedAppId, type, open, onClose, } = props; const navigate = useNavigate(); const [listLoading, setListLoading] = useState(false); type List = { name: string, chatMode: string, appId: string, desc: string, createTime: string, typeName: string, isCollect: boolean, }[]; const [list, setList] = useState([]); const fetchAppList = async () => { setListLoading(true); try { const res = await api.get(`/deepseek/api/project/app`); // // 确保 res.data 是数组,如果不是则设为空数组 error // const data = Array.isArray(res.data) ? res.data : []; if (type === 'all') { setList(res.data); } else { setList(res.data.filter((item: any) => item.isCollect)); } } catch (error) { console.error(error); // // 出错时设置为空数组,避免渲染错误 error // setList([]); } finally { setListLoading(false); } }; // 收藏应用 const collectApp = async (appId: string) => { try { await api.post('/deepseek/api/app/collect', { appId: appId }); message.success('收藏成功'); await fetchAppList(); } catch (error: any) { message.error(error.msg); } }; // 取消收藏应用 const cancelCollectApp = async (appId: string) => { try { await api.delete(`/deepseek/api/app/collect/${appId}`); message.success('操作成功'); await fetchAppList(); } catch (error: any) { message.error(error.msg); } }; const init = async () => { await fetchAppList(); } useEffect(() => { init(); }, []) return ( { list.length > 0 ? // Array.isArray(list) && list.length > 0 ? //error list.map((item, index) => { return
{item.name}
ID:{item.appId}
{item.desc}
{dayjs(item.createTime).format('YYYY-MM-DD')} 发布
{item.typeName}
{ if (item.isCollect) { await cancelCollectApp(item.appId); } else { await collectApp(item.appId); } }}> { item.isCollect ? : }
}) : }
) } 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'].includes(location.pathname)) { return 'deepSeek'; } else { return 'bigModel'; } } // 获取聊天列表 const fetchChatList = async (chatMode?: 'ONLINE' | 'LOCAL') => { try { let url = ''; if (getType() === 'bigModel') { const appId = globalStore.selectedAppId; if (appId) { if (chatMode === 'LOCAL') { url = `/deepseek/api/dialog/list/${appId}`; } else { url = `/bigmodel/api/dialog/list/${appId}`; } } } else { const appId = '1881269958412521255'; url = `/bigmodel/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: ( { 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(chatStore.chatMode); } } }, [globalStore.selectedAppId]); useEffect(() => { 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'); return ( <> { globalStore.showMenu && { getType() === 'deepSeek' &&
} { isMobileScreen &&
} 问答历史 : '' } logo={getType() === 'bigModel' ? : ''} >
{ const key = info.key; // @ts-ignore const props = info.item.props; const { showMenu, chatMode, appId } = props; if (isMobileScreen) { globalStore.setShowMenu(false); } let url = ``; if (getType() === 'bigModel') { if (chatStore.chatMode === 'LOCAL') { url = `/deepseek/api/dialog/detail/${key}`; } else { url = `/bigmodel/api/dialog/detail/${key}`; } } else { url = `/bigmodel/api/dialog/detail/${key}`; } const res = await api.get(url); const list = res.data.map(((item: any) => { if(item.sliceInfo){ let allChunkNum = 0; item.sliceInfo.doc.forEach((doc: any) => { allChunkNum += doc.chunk_nums; }); item.sliceInfo.allChunkNum = allChunkNum; } return { id: item.did, role: item.type, date: item.create_time, content: item.content, document: item.document ? item.document : undefined, sliceInfo: item.sliceInfo ? item.sliceInfo : undefined, networkInfo: item.networkInfo ? item.networkInfo : undefined, } })) const session = { appId: res.data.length ? res.data[0].appId : '', dialogName: res.data.length ? res.data[0].dialog_name : '', id: res.data.length ? res.data[0].id : '', messages: list, } globalStore.setCurrentSession(session); chatStore.clearSessions(); chatStore.updateCurrentSession((value) => { value.appId = session.appId; value.topic = session.dialogName; value.id = session.id; value.messages = list; }); if (getType() === 'bigModel') { const search = `?showMenu=${showMenu}&chatMode=${chatMode}&appId=${appId}`; if (appId) { navigate({ pathname: '/knowledgeChat', search: search, }) } } else { navigate({ pathname: '/newDeepseekChat' }); } }} mode="inline" items={menuList} /> { form.validateFields().then(async (values) => { setModalOpen(false); try { if (getType() === 'bigModel') { if (chatStore.chatMode === 'LOCAL') { await api.put(`/deepseek/api/dialog/update`, { id: values.dialogId, dialogName: values.dialogName }); await fetchChatList(chatStore.chatMode); } else { await api.put(`/bigmodel/api/dialog/update`, { id: values.dialogId, dialogName: values.dialogName }); await fetchChatList(); } } else { await api.put(`/bigmodel/api/dialog/update`, { id: values.dialogId, dialogName: values.dialogName }); await fetchChatList(); } chatStore.updateCurrentSession((value) => { value.topic = values.dialogName; }); } catch (error) { console.error(error); } }).catch((error) => { console.error(error); }); }} onCancel={() => { setModalOpen(false); }} >
} { drawerOpen && { setDrawerOpen(false); }} /> } ); }