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 } 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 (
{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 是数组,如果不是则设为空数组
const data = Array.isArray(res.data) ? res.data : [];
if (type === 'all') {
setList(data);
} else {
setList(data.filter((item: any) => item.isCollect));
}
} catch (error) {
console.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 (
{
Array.isArray(list) && list.length > 0 ?
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 &&
}
onClick={() => {
globalStore.setShowMenu(!globalStore.showMenu);
}}
/>
}
问答历史
:
''
}
logo={getType() === 'bigModel' ?
: ''}
>
}
{
drawerOpen &&
{
setDrawerOpen(false);
}}
/>
}
>
);
}