sidebar.tsx 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  1. import React, { useEffect, useRef, useMemo, useState, Fragment } from "react";
  2. import Image from 'next/image';
  3. import styles from "./home.module.scss";
  4. import newStyles from './sidebar.module.scss';
  5. import DragIcon from "../icons/drag.svg";
  6. import logoSrc from "../icons/logo.png";
  7. import deepSeekSrc from "../icons/deepSeek.png";
  8. import { AppstoreOutlined, EditOutlined, MenuOutlined, HomeOutlined, PlusOutlined, StarOutlined, CommentOutlined } from '@ant-design/icons';
  9. import * as AllIcons from '@ant-design/icons';
  10. import { useAppConfig, useChatStore, useGlobalStore } from "../store";
  11. import {
  12. DEFAULT_SIDEBAR_WIDTH,
  13. MAX_SIDEBAR_WIDTH,
  14. MIN_SIDEBAR_WIDTH,
  15. NARROW_SIDEBAR_WIDTH,
  16. } from "../constant";
  17. import { useLocation, useNavigate } from "react-router-dom";
  18. import { isIOS, useMobileScreen, getContrastColor } from "../utils";
  19. import api from "@/app/api/api";
  20. import { Button, Drawer, Dropdown, Empty, Form, Input, Menu, message, Modal, Rate, Tag, Select } from "antd";
  21. import { downloadFile } from "../utils/index";
  22. import dayjs from "dayjs";
  23. import type { DrawerProps, RadioChangeEvent } from 'antd';
  24. import '@/app/styles/common.scss'
  25. const FormItem = Form.Item;
  26. import { processSliceData } from "@/app/utils/index";
  27. export function useHotKey() {
  28. const chatStore = useChatStore();
  29. useEffect(() => {
  30. const onKeyDown = (e: KeyboardEvent) => {
  31. if (e.altKey || e.ctrlKey) {
  32. if (e.key === "ArrowUp") {
  33. chatStore.nextSession(-1);
  34. } else if (e.key === "ArrowDown") {
  35. chatStore.nextSession(1);
  36. }
  37. }
  38. };
  39. window.addEventListener("keydown", onKeyDown);
  40. return () => window.removeEventListener("keydown", onKeyDown);
  41. });
  42. }
  43. export function useDragSideBar() {
  44. const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
  45. const config = useAppConfig();
  46. const startX = useRef(0);
  47. const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
  48. const lastUpdateTime = useRef(Date.now());
  49. const toggleSideBar = () => {
  50. config.update((config) => {
  51. if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) {
  52. config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;
  53. } else {
  54. config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
  55. }
  56. });
  57. };
  58. const onDragStart = (e: MouseEvent) => {
  59. // Remembers the initial width each time the mouse is pressed
  60. startX.current = e.clientX;
  61. startDragWidth.current = config.sidebarWidth;
  62. const dragStartTime = Date.now();
  63. const handleDragMove = (e: MouseEvent) => {
  64. if (Date.now() < lastUpdateTime.current + 20) {
  65. return;
  66. }
  67. lastUpdateTime.current = Date.now();
  68. const d = e.clientX - startX.current;
  69. const nextWidth = limit(startDragWidth.current + d);
  70. config.update((config) => {
  71. if (nextWidth < MIN_SIDEBAR_WIDTH) {
  72. config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
  73. } else {
  74. config.sidebarWidth = nextWidth;
  75. }
  76. });
  77. };
  78. const handleDragEnd = () => {
  79. // In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth
  80. window.removeEventListener("pointermove", handleDragMove);
  81. window.removeEventListener("pointerup", handleDragEnd);
  82. // if user click the drag icon, should toggle the sidebar
  83. const shouldFireClick = Date.now() - dragStartTime < 300;
  84. if (shouldFireClick) {
  85. toggleSideBar();
  86. }
  87. };
  88. window.addEventListener("pointermove", handleDragMove);
  89. window.addEventListener("pointerup", handleDragEnd);
  90. };
  91. const isMobileScreen = useMobileScreen();
  92. const shouldNarrow =
  93. !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
  94. useEffect(() => {
  95. const barWidth = shouldNarrow
  96. ? NARROW_SIDEBAR_WIDTH
  97. : limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
  98. const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
  99. document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
  100. }, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
  101. return {
  102. onDragStart,
  103. shouldNarrow,
  104. };
  105. }
  106. export function SideBarContainer(props: {
  107. children: React.ReactNode;
  108. onDragStart: (e: MouseEvent) => void;
  109. shouldNarrow: boolean;
  110. className?: string;
  111. }) {
  112. const isMobileScreen = useMobileScreen();
  113. const isIOSMobile = useMemo(
  114. () => isIOS() && isMobileScreen,
  115. [isMobileScreen],
  116. );
  117. const { children, className, onDragStart, shouldNarrow } = props;
  118. // shadow-sidebar
  119. return (
  120. <div
  121. className={`${styles.sidebar} ${className} ${shouldNarrow && styles["narrow-sidebar"]}
  122. bg-light-sidebar ${isMobileScreen && newStyles.isMobildwid}`}
  123. style={{
  124. transition: isMobileScreen && isIOSMobile ? "none" : undefined,
  125. overflowY: "auto",
  126. }}
  127. >
  128. {children}
  129. <div
  130. className={styles["sidebar-drag"]}
  131. onPointerDown={(e) => onDragStart(e as any)}
  132. >
  133. <DragIcon />
  134. </div>
  135. </div>
  136. );
  137. }
  138. // Sidebar 头部
  139. export function SideBarHeader(props: {
  140. title?: string | React.ReactNode;
  141. subTitle?: string | React.ReactNode;
  142. logo?: React.ReactNode;
  143. children?: React.ReactNode;
  144. }) {
  145. const { title, subTitle, logo, children } = props;
  146. const navigate = useNavigate();
  147. return (
  148. <Fragment>
  149. <div className={`${styles["sidebar-header"]} cursor-pointer`} data-tauri-drag-region onClick={() => {
  150. window.open('http://10.1.14.17:3200/appCenter')
  151. }} >
  152. <div className={styles["sidebar-logo"] + " no-dark"}>{logo}</div>
  153. <div className={styles["sidebar-title-container"] + ' ml-[10px]'}>
  154. <div className={`${styles["sidebar-title"]} text-gray-800`} data-tauri-drag-region>
  155. {title}
  156. </div>
  157. <div className={`${styles["sidebar-sub-title"]} text-gray-500 text-sm`}>{subTitle}</div>
  158. </div>
  159. </div>
  160. {children}
  161. </Fragment>
  162. );
  163. }
  164. export function SideBarBody(props: {
  165. children: React.ReactNode;
  166. onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
  167. }) {
  168. const { onClick, children } = props;
  169. return (
  170. <div className={`${styles["sidebar-body"]} scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent`} onClick={onClick}>
  171. {children}
  172. </div>
  173. );
  174. }
  175. export function SideBarTail(props: {
  176. primaryAction?: React.ReactNode;
  177. secondaryAction?: React.ReactNode;
  178. }) {
  179. const { primaryAction, secondaryAction } = props;
  180. return (
  181. <div className={`${styles["sidebar-tail"]} border-t border-gray-200 pt-4`}>
  182. <div className={styles["sidebar-actions"]}>{primaryAction}</div>
  183. <div className={styles["sidebar-actions"]}>{secondaryAction}</div>
  184. </div>
  185. );
  186. }
  187. interface AppDrawerProps {
  188. isMobileScreen: boolean,
  189. selectedAppId: string,
  190. type: 'all' | 'collect',
  191. open: boolean,
  192. onClose: () => void,
  193. }
  194. export const SideBar = (props: { className?: string }) => {
  195. // useHotKey();
  196. const { onDragStart, shouldNarrow } = useDragSideBar();
  197. const [showPluginSelector, setShowPluginSelector] = useState(false);
  198. const navigate = useNavigate();
  199. const location = useLocation();
  200. const chatStore = useChatStore();
  201. const globalStore = useGlobalStore();
  202. const [menuList, setMenuList] = useState([])
  203. const [modalOpen, setModalOpen] = useState(false)
  204. const [form] = Form.useForm();
  205. const getType = (): 'bigModel' | 'deepSeek' => {
  206. if (['/knowledgeChat', '/newChat'].includes(location.pathname)) {
  207. return 'bigModel';
  208. } else if (['/deepseekChat', '/newDeepseekChat','/welcome'].includes(location.pathname)) {
  209. return 'deepSeek';
  210. } else {
  211. return 'bigModel';
  212. }
  213. }
  214. // 获取应用类型 app_type
  215. const fetchAppType = async () => {
  216. try {
  217. const res = await api.get(`/deepseek/api/app_type`);
  218. // 解析返回并设置状态
  219. if (res && res.data) {
  220. setAppTypes([{ dictLabel: '收藏', dictValue: '收藏' }, ...res.data]);
  221. }
  222. } catch (error) {
  223. console.error('Failed to fetch app types:', error);
  224. }
  225. }
  226. // 获取应用列表
  227. const fetchGetApplicationList = async (typeId?: string | null, name?: string) => {
  228. setAppListLoading(true);
  229. try {
  230. const data = {
  231. pageSize: 1000,
  232. pageNum: 1,
  233. userId: 1,
  234. isCollect: typeId === '收藏' ? '1' : null,
  235. typeId: typeId === '收藏' ? null : typeId,
  236. name: name,
  237. }
  238. const res: any = await api.post('/deepseek/api/getApplicationList', data);
  239. // 解析返回并设置状态
  240. if (res && res.rows) {
  241. setAppListState(res.rows);
  242. if (name) {
  243. setSearchOptions(res.rows.map((item: any) => ({ label: item.name, value: item.appId })));
  244. }
  245. }
  246. } catch (error) {
  247. console.error('Failed to fetch app list:', error);
  248. } finally {
  249. setAppListLoading(false);
  250. setSearchFetching(false);
  251. }
  252. }
  253. // 应用类型与列表状态
  254. const [appTypes, setAppTypes] = useState<any[]>([]);
  255. const [appListState, setAppListState] = useState<any[]>([]);
  256. const [appListLoading, setAppListLoading] = useState<boolean>(false);
  257. const [openKeys, setOpenKeys] = useState<string[]>([]); // 当前打开的菜单项
  258. // 渲染应用列表为 Antd Menu(一级:类型,带图标;二级:该类型下的应用)
  259. const previewAppList = () => {
  260. const iconFor = (index: number) => {
  261. const icons = [<AppstoreOutlined />, <StarOutlined />, <HomeOutlined />];
  262. return icons[index % icons.length];
  263. };
  264. const items = appTypes && appTypes.length
  265. ? appTypes.map((t: any, idx: number) => {
  266. // 当该一级是当前打开项时,使用 appListState 作为 children(fetchGetApplicationList 填充)
  267. const typeKey = `${t.dictValue}`;
  268. let children = [] as any[];
  269. // if (openKeys.includes(typeKey)) {
  270. if (appListLoading) {
  271. children = [{ key: `${typeKey}-loading`, label: <span>加载中...</span> }];
  272. } else {
  273. children = (appListState || []).map((a: any, i: number) => ({
  274. key: a.appId || `app-${idx}-${i}`,
  275. // label: a.dictLabel || a.name || a.appName || `应用 ${i}`,
  276. label: a.iconColor ? (() => {
  277. const C = (AllIcons as any)[a.iconType];
  278. const iconColor = getContrastColor(a.iconColor);
  279. return C ? <div className="flex items-center justify-start">
  280. <p className="flex items-center justify-center" style={{ overflow: 'auto', background: a.iconColor, minWidth: '28px', width: 28, height: 28, borderRadius: 8, padding: 0, margin: 0, marginRight: 4 }}>
  281. <C style={{ fontSize: 28, color: iconColor }} />
  282. </p>
  283. <span className="truncate ml-2">
  284. {a.name || a.appName || `应用 ${i}`}
  285. </span>
  286. </div> : <span style={{ fontSize: 12 }}>{a.iconType}</span>
  287. })() : <span className="truncate">
  288. {a.name || a.appName || `应用 ${i}`}
  289. </span>,
  290. onClick: () => {
  291. chatStore.updateCurrentSession((value) => {
  292. value.appId = a.appId;
  293. });
  294. // 点击二级应用的处理:打印或导航(保留为 UI 先)
  295. chatStore.clearSessions();
  296. if (getType() === 'bigModel') {
  297. globalStore.setSelectedAppId(a.appId);
  298. } else {
  299. const search = `?showMenu=false&chatMode=LOCAL&appId=${a.appId}`;
  300. navigate({
  301. pathname: '/knowledgeChat',
  302. search: search,
  303. })
  304. globalStore.setSelectedAppId(a.appId);
  305. // location.reload();
  306. }
  307. }
  308. }));
  309. }
  310. // }
  311. if (openKeys.includes(typeKey)) {
  312. return {
  313. key: t.dictValue,
  314. icon: iconFor(idx),
  315. label: t.dictLabel || `类型 ${idx}`,
  316. children: children.length ? children : [{ key: `empty-${idx}`, label: <span className="text-xs text-gray-400">暂无应用</span>, }],
  317. };
  318. } else {
  319. return {
  320. key: t.dictValue,
  321. icon: iconFor(idx),
  322. label: t.dictLabel || `类型 ${idx}`,
  323. children: [],
  324. };
  325. }
  326. })
  327. : [];
  328. return (
  329. <Menu
  330. items={items}
  331. mode="inline"
  332. openKeys={openKeys}
  333. selectable={false}
  334. className={`bg-transparent p-[0] ${newStyles.sidebarContainer}`}
  335. style={{ border: 'none', background: 'transparent' }}
  336. onOpenChange={(e) => {
  337. console.log('e', e);
  338. if (e.length > 0) {
  339. setOpenKeys(e.slice(-1));
  340. fetchGetApplicationList(e.slice(-1)[0]);
  341. } else {
  342. setOpenKeys([]);
  343. }
  344. }}
  345. />
  346. );
  347. }
  348. // 获取聊天列表
  349. const fetchChatList = async (chatMode?: 'ONLINE' | 'LOCAL') => {
  350. try {
  351. let url = '';
  352. const appId = globalStore.selectedAppId;
  353. if (appId) {
  354. url = `/deepseek/api/dialog/list/${appId}`;
  355. const res = await api.get(url);
  356. const list = res.data.map((item: any) => {
  357. return {
  358. ...item,
  359. children: item.children.map((child: any) => {
  360. const items = [
  361. {
  362. key: '1',
  363. label: (
  364. <a onClick={(e: React.MouseEvent) => {
  365. e.preventDefault();
  366. e.stopPropagation();
  367. setModalOpen(true);
  368. form.setFieldsValue({
  369. dialogId: child.key,
  370. dialogName: child.label
  371. });
  372. }}>
  373. 重命名
  374. </a>
  375. ),
  376. },
  377. {
  378. key: '2',
  379. label: (
  380. <a onClick={async () => {
  381. try {
  382. let blob = null;
  383. if (getType() === 'bigModel') {
  384. if (chatMode === 'LOCAL') {
  385. blob = await api.post(`/deepseek/api/dialog/export/${child.key}`, {}, { responseType: 'blob' });
  386. } else {
  387. blob = await api.post(`/bigmodel/api/dialog/export/${child.key}`, {}, { responseType: 'blob' });
  388. }
  389. } else {
  390. blob = await api.post(`/bigmodel/api/dialog/export/${child.key}`, {}, { responseType: 'blob' });
  391. }
  392. const fileName = `${child.label}.xlsx`;
  393. downloadFile(blob, fileName);
  394. } catch (error) {
  395. console.error(error);
  396. }
  397. }}>
  398. 导出
  399. </a>
  400. ),
  401. },
  402. {
  403. key: '3',
  404. label: (
  405. <a onClick={async () => {
  406. try {
  407. if (getType() === 'bigModel') {
  408. if (chatMode === 'LOCAL') {
  409. await api.delete(`/deepseek/api/dialog/del/${child.key}`);
  410. await fetchChatList(chatMode);
  411. } else {
  412. await api.delete(`/bigmodel/api/dialog/del/${child.key}`);
  413. await fetchChatList();
  414. }
  415. } else {
  416. await api.delete(`/bigmodel/api/dialog/del/${child.key}`);
  417. await fetchChatList();
  418. }
  419. chatStore.clearSessions();
  420. useChatStore.setState({
  421. message: {
  422. content: '',
  423. role: 'assistant',
  424. }
  425. });
  426. } catch (error) {
  427. console.error(error);
  428. }
  429. }}>
  430. 删除
  431. </a>
  432. ),
  433. },
  434. ];
  435. return {
  436. ...child,
  437. label: <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
  438. <div style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', marginRight: 10 }}>
  439. {child.label}
  440. </div>
  441. <div style={{ width: 20 }}>
  442. <Dropdown menu={{ items }} trigger={['click']} placement="bottomRight">
  443. <EditOutlined onClick={(e) => e.stopPropagation()} />
  444. </Dropdown>
  445. </div>
  446. </div>
  447. }
  448. })
  449. }
  450. })
  451. setMenuList(list);
  452. }
  453. } catch (error) {
  454. console.error(error)
  455. }
  456. }
  457. useEffect(() => {
  458. // if (getType() === 'bigModel') {
  459. if (globalStore.selectedAppId) {
  460. fetchChatList('LOCAL');
  461. }
  462. // }
  463. }, [globalStore.selectedAppId]);
  464. useEffect(() => {
  465. fetchAppType();
  466. chatStore.clearSessions();
  467. useChatStore.setState({
  468. message: {
  469. content: '',
  470. role: 'assistant',
  471. }
  472. });
  473. }, []);
  474. useEffect(() => {
  475. fetchChatList(chatStore.chatMode);
  476. }, [chatStore.chatMode]);
  477. const isMobileScreen = useMobileScreen();
  478. const [drawerOpen, setDrawerOpen] = useState(false);
  479. const [drawerType, setDrawerType] = useState<'all' | 'collect'>('all');
  480. // Select 远程搜索 UI 状态(UI-only 模拟)
  481. const [searchOptions, setSearchOptions] = useState<any[]>([]);
  482. const [searchFetching, setSearchFetching] = useState(false);
  483. const handleSearch = (value: string) => {
  484. console.log('search value', value);
  485. if (!value) {
  486. setSearchOptions([]);
  487. return;
  488. }
  489. setSearchFetching(true);
  490. fetchGetApplicationList(null, value);
  491. // 模拟异步请求
  492. };
  493. const [placement, setPlacement] = useState<DrawerProps['placement']>('left');
  494. return (
  495. <>
  496. {
  497. isMobileScreen ? globalStore.showMenu &&
  498. <Drawer
  499. title="Basic Drawer"
  500. placement={placement}
  501. closable={true}
  502. maskClosable={true}
  503. onClose={(e) => {
  504. console.log('close drawer');
  505. e.preventDefault();
  506. e.stopPropagation();
  507. globalStore.setShowMenu(false);
  508. }}
  509. open={globalStore.showMenu}
  510. key={placement}
  511. style={{
  512. // 1. 自定义 Drawer 整体背景色(包括头部、内容区)
  513. background: 'none', // 浅灰背景,可替换为 #fff、rgb(255,255,255) 等
  514. width: '100%',
  515. }}
  516. styles={{
  517. mask: {
  518. zIndex: 1000, // 遮罩层层级(原 maskStyle 中的配置)
  519. background: 'rgba(0, 0, 0, 0.3)', // 遮罩层背景色/透明度
  520. // 其他遮罩层样式均可在此配置,与原 maskStyle 用法一致
  521. },
  522. }}
  523. >
  524. <SideBarContainer
  525. onDragStart={onDragStart}
  526. shouldNarrow={shouldNarrow}
  527. {...props}
  528. >
  529. {/* {
  530. getType() === 'deepSeek' &&
  531. <div>
  532. <img style={{ width: '100%' }} src={deepSeekSrc.src} />
  533. </div>
  534. } */}
  535. <SideBarHeader
  536. title={getType() === 'bigModel' || true ?
  537. <div className="flex items-center">
  538. {/* {
  539. isMobileScreen && <div>
  540. <Button
  541. type='text'
  542. icon={<MenuOutlined />}
  543. onClick={() => {
  544. globalStore.setShowMenu(!globalStore.showMenu);
  545. }}
  546. />
  547. </div>
  548. } */}
  549. <img style={{ height: 40 }} src={logoSrc.src} />
  550. {/* 盈科 */}
  551. </div>
  552. :
  553. ''
  554. }
  555. // logo={getType() === 'bigModel' || true ? <img style={{ height: 40 }} src={logoSrc.src} /> : ''}
  556. >
  557. <div className="w-full">
  558. <Button type="primary"
  559. icon={<PlusOutlined />}
  560. className="border border-[#4096ff] text-[#4096ff] bg-transparent w-full mb-[10px]"
  561. onClick={async () => {
  562. chatStore.clearSessions();
  563. chatStore.updateCurrentSession((value) => {
  564. value.appId = globalStore.selectedAppId;
  565. });
  566. if (isMobileScreen) {
  567. globalStore.setShowMenu(false);
  568. }
  569. if (getType() === 'bigModel') {
  570. navigate({ pathname: '/newChat' });
  571. } else {
  572. message.info('请选择应用')
  573. // navigate({ pathname: '/newDeepseekChat' });
  574. }
  575. if (getType() === 'bigModel') {
  576. // if (chatStore.chatMode === 'LOCAL') {
  577. await fetchChatList(chatStore.chatMode);
  578. // } else {
  579. // await fetchChatList();
  580. // }
  581. } else {
  582. // await fetchChatList();
  583. }
  584. }}
  585. >
  586. 新建对话
  587. </Button>
  588. </div>
  589. {/* 搜索框 - antd Select 远程搜索(UI only) */}
  590. <div className="mb-[5px] text-left">
  591. <Select
  592. className="text-left bg-transparent w-full"
  593. showSearch
  594. allowClear
  595. placeholder="搜索应用名称"
  596. filterOption={false}
  597. onSearch={handleSearch}
  598. options={searchOptions}
  599. notFoundContent={searchFetching ? '搜索中...' : '无匹配'}
  600. onChange={(appId) => {
  601. // 选择后可触发打开抽屉或填写表单等动作(目前只做UI)
  602. console.log('select val', appId);
  603. chatStore.updateCurrentSession((value) => {
  604. value.appId = appId;
  605. });
  606. // 点击二级应用的处理:打印或导航(保留为 UI 先)
  607. chatStore.clearSessions();
  608. if (getType() === 'bigModel') {
  609. globalStore.setSelectedAppId(appId);
  610. } else {
  611. const search = `?showMenu=false&chatMode=LOCAL&appId=${appId}`;
  612. navigate({
  613. pathname: '/knowledgeChat',
  614. search: search,
  615. })
  616. globalStore.setSelectedAppId(appId);
  617. // location.reload();
  618. }
  619. }}
  620. style={{ width: '100%', textAlign: 'left', background: 'transparent' }}
  621. />
  622. </div>
  623. {/* 应用列表 */}
  624. {previewAppList()}
  625. </SideBarHeader>
  626. {/* 最近对话 */}
  627. {menuList.length > 0 && <p className="text-[14px] ml-[6px]"> <CommentOutlined /> 最近对话</p>}
  628. <Menu
  629. className="bg-transparent"
  630. style={{ border: 'none', background: 'transparent' }}
  631. selectable={false}
  632. onClick={async (info: any) => {
  633. const key = info.key;
  634. // @ts-ignore
  635. const props = info.item?.props;
  636. const { showMenu, chatMode, appId } = props;
  637. if (isMobileScreen) {
  638. globalStore.setShowMenu(false);
  639. }
  640. let url = ``;
  641. if (getType() === 'bigModel') {
  642. if (chatStore.chatMode === 'LOCAL') {
  643. url = `/deepseek/api/dialog/detail/${key}`;
  644. }
  645. } else {
  646. url = `/bigmodel/api/dialog/detail/${key}`;
  647. }
  648. const res = await api.get(url);
  649. const list = res.data.map(((item: any) => {
  650. if (item.sliceInfo) {
  651. let allChunkNum = 0;
  652. item.sliceInfo.doc.forEach((doc: any) => {
  653. allChunkNum += doc.chunk_nums;
  654. });
  655. item.sliceInfo.allChunkNum = allChunkNum;
  656. const values1 = item.sliceInfo?.doc;
  657. const result = processSliceData(values1);
  658. // 使用解构赋值,让结果更清晰
  659. const { withDeprecated, withoutDeprecated } = result;
  660. item.sliceInfo.docDeprecated = withDeprecated;
  661. item.sliceInfo.docActive = withoutDeprecated;
  662. console.log('item.sliceInfo', item.sliceInfo)
  663. }
  664. return {
  665. id: item.did,
  666. role: item.type,
  667. date: item.create_time,
  668. content: item.content,
  669. document: item.document ? item.document : undefined,
  670. sliceInfo: item.sliceInfo ? item.sliceInfo : undefined,
  671. delSliceInfo: item.sliceInfo ? item.sliceInfo : undefined,
  672. networkInfo: item.networkInfo ? item.networkInfo : undefined,
  673. }
  674. }))
  675. const session = {
  676. appId: res.data.length ? res.data[0].appId : '',
  677. dialogName: res.data.length ? res.data[0].dialog_name : '',
  678. id: res.data.length ? res.data[0].id : '',
  679. messages: list,
  680. }
  681. globalStore.setCurrentSession(session);
  682. chatStore.clearSessions();
  683. chatStore.updateCurrentSession((value) => {
  684. value.appId = session.appId;
  685. value.topic = session.dialogName;
  686. value.id = session.id;
  687. value.messages = list;
  688. });
  689. if (getType() === 'bigModel') {
  690. const search = `?showMenu=${showMenu}&chatMode=${chatMode}&appId=${appId}`;
  691. if (appId) {
  692. navigate({
  693. pathname: '/knowledgeChat',
  694. search: search,
  695. })
  696. }
  697. }
  698. // else {
  699. // navigate({ pathname: '/newDeepseekChat' });
  700. // }
  701. }}
  702. mode="inline"
  703. items={menuList}
  704. />
  705. <Modal
  706. title="重命名"
  707. open={modalOpen}
  708. width={300}
  709. maskClosable={false}
  710. onOk={() => {
  711. form.validateFields().then(async (values) => {
  712. setModalOpen(false);
  713. try {
  714. if (getType() === 'bigModel') {
  715. if (chatStore.chatMode === 'LOCAL') {
  716. await api.put(`/deepseek/api/dialog/update`, {
  717. id: values.dialogId,
  718. dialogName: values.dialogName
  719. });
  720. await fetchChatList(chatStore.chatMode);
  721. } else {
  722. await api.put(`/bigmodel/api/dialog/update`, {
  723. id: values.dialogId,
  724. dialogName: values.dialogName
  725. });
  726. await fetchChatList();
  727. }
  728. } else {
  729. await api.put(`/bigmodel/api/dialog/update`, {
  730. id: values.dialogId,
  731. dialogName: values.dialogName
  732. });
  733. await fetchChatList();
  734. }
  735. chatStore.updateCurrentSession((value) => {
  736. value.topic = values.dialogName;
  737. });
  738. } catch (error) {
  739. console.error(error);
  740. }
  741. }).catch((error) => {
  742. console.error(error);
  743. });
  744. }}
  745. onCancel={() => {
  746. setModalOpen(false);
  747. }}
  748. >
  749. <Form form={form} layout='inline'>
  750. <FormItem name='dialogId' noStyle />
  751. <FormItem
  752. label='名称'
  753. name='dialogName'
  754. rules={[{ required: true, message: '名称不能为空', whitespace: true }]}
  755. >
  756. <Input
  757. style={{ width: 300 }}
  758. placeholder='请输入'
  759. maxLength={20}
  760. />
  761. </FormItem>
  762. </Form>
  763. </Modal>
  764. </SideBarContainer>
  765. </Drawer> :
  766. globalStore.showMenu && <SideBarContainer
  767. onDragStart={onDragStart}
  768. shouldNarrow={shouldNarrow}
  769. {...props}
  770. >
  771. {/* {
  772. getType() === 'deepSeek' &&
  773. <div>
  774. <img style={{ width: '100%' }} src={deepSeekSrc.src} />
  775. </div>
  776. } */}
  777. <SideBarHeader
  778. title={getType() === 'bigModel' || true ?
  779. <div className="flex items-center">
  780. {
  781. isMobileScreen && <div>
  782. <Button
  783. type='text'
  784. icon={<MenuOutlined />}
  785. onClick={() => {
  786. globalStore.setShowMenu(!globalStore.showMenu);
  787. }}
  788. />
  789. </div>
  790. }
  791. <img style={{ height: 40 }} src={logoSrc.src} />
  792. {/* 盈科2 */}
  793. </div>
  794. :
  795. ''
  796. }
  797. // logo={getType() === 'bigModel' || true ? <Logosvg></Logosvg> : ''}
  798. >
  799. <div className="w-full">
  800. <Button type="primary"
  801. icon={<PlusOutlined />}
  802. className="border border-[#4096ff] text-[#4096ff] bg-transparent w-full mb-[10px]"
  803. onClick={async () => {
  804. chatStore.clearSessions();
  805. chatStore.updateCurrentSession((value) => {
  806. value.appId = globalStore.selectedAppId;
  807. });
  808. if (isMobileScreen) {
  809. globalStore.setShowMenu(false);
  810. }
  811. if (getType() === 'bigModel') {
  812. navigate({ pathname: '/newChat' });
  813. } else {
  814. // navigate({ pathname: '/newDeepseekChat' });
  815. message.info('请选择应用')
  816. }
  817. if (getType() === 'bigModel') {
  818. if (chatStore.chatMode === 'LOCAL') {
  819. await fetchChatList(chatStore.chatMode);
  820. // } else {
  821. // await fetchChatList();
  822. }
  823. }
  824. // else {
  825. // await fetchChatList();
  826. // }
  827. }}
  828. >
  829. 新建对话
  830. </Button>
  831. </div>
  832. {/* 搜索框 - antd Select 远程搜索(UI only) */}
  833. <div className="mb-[5px] text-left">
  834. <Select
  835. className="text-left bg-transparent w-full"
  836. showSearch
  837. allowClear
  838. placeholder="搜索应用名称"
  839. filterOption={false}
  840. onSearch={handleSearch}
  841. options={searchOptions}
  842. notFoundContent={searchFetching ? '搜索中...' : '无匹配'}
  843. onChange={(appId) => {
  844. // 选择后可触发打开抽屉或填写表单等动作(目前只做UI)
  845. console.log('select val', appId);
  846. // 选择后可触发打开抽屉或填写表单等动作(目前只做UI)
  847. console.log('select val', appId);
  848. chatStore.updateCurrentSession((value) => {
  849. value.appId = appId;
  850. });
  851. // 点击二级应用的处理:打印或导航(保留为 UI 先)
  852. chatStore.clearSessions();
  853. if (getType() === 'bigModel') {
  854. globalStore.setSelectedAppId(appId);
  855. } else {
  856. const search = `?showMenu=false&chatMode=LOCAL&appId=${appId}`;
  857. navigate({
  858. pathname: '/knowledgeChat',
  859. search: search,
  860. })
  861. globalStore.setSelectedAppId(appId);
  862. // location.reload();
  863. }
  864. }}
  865. style={{ width: '100%', textAlign: 'left', background: 'transparent' }}
  866. />
  867. </div>
  868. {/* 应用列表 */}
  869. {previewAppList()}
  870. </SideBarHeader>
  871. {/* 最近对话 */}
  872. {menuList.length > 0 && <p className="text-[14px] ml-[6px]"> <CommentOutlined /> 最近对话</p>}
  873. <Menu
  874. className="bg-transparent"
  875. style={{ border: 'none', background: 'transparent' }}
  876. selectable={false}
  877. onClick={async (info: any) => {
  878. const key = info.key;
  879. // @ts-ignore
  880. const props = info.item?.props;
  881. const { showMenu, chatMode, appId } = props;
  882. if (isMobileScreen) {
  883. globalStore.setShowMenu(false);
  884. }
  885. let url = ``;
  886. if (getType() === 'bigModel') {
  887. if (chatStore.chatMode === 'LOCAL') {
  888. url = `/deepseek/api/dialog/detail/${key}`;
  889. }
  890. } else {
  891. url = `/bigmodel/api/dialog/detail/${key}`;
  892. }
  893. const res = await api.get(url);
  894. const list = res.data.map(((item: any) => {
  895. if (item.sliceInfo) {
  896. let allChunkNum = 0;
  897. item.sliceInfo.doc.forEach((doc: any) => {
  898. allChunkNum += doc.chunk_nums;
  899. });
  900. item.sliceInfo.allChunkNum = allChunkNum;
  901. const values1 = item.sliceInfo?.doc;
  902. // console.log('values1', values1);
  903. const result = processSliceData(values1);
  904. // console.log('result---',result)
  905. // 使用解构赋值,让结果更清晰
  906. const { withDeprecated, withoutDeprecated } = result;
  907. item.sliceInfo.docDeprecated = withDeprecated;
  908. item.sliceInfo.docActive = withoutDeprecated;
  909. // console.log('item.sliceInfo',item.sliceInfo)
  910. }
  911. return {
  912. id: item.did,
  913. role: item.type,
  914. date: item.create_time,
  915. content: item.content,
  916. document: item.document ? item.document : undefined,
  917. sliceInfo: item.sliceInfo ? item.sliceInfo : undefined,
  918. delSliceInfo: item.sliceInfo ? item.sliceInfo : undefined,
  919. networkInfo: item.networkInfo ? item.networkInfo : undefined,
  920. }
  921. }))
  922. const session = {
  923. appId: res.data.length ? res.data[0].appId : '',
  924. dialogName: res.data.length ? res.data[0].dialog_name : '',
  925. id: res.data.length ? res.data[0].id : '',
  926. messages: list,
  927. }
  928. globalStore.setCurrentSession(session);
  929. chatStore.clearSessions();
  930. chatStore.updateCurrentSession((value) => {
  931. value.appId = session.appId;
  932. value.topic = session.dialogName;
  933. value.id = session.id;
  934. value.messages = list;
  935. });
  936. if (getType() === 'bigModel') {
  937. const search = `?showMenu=${showMenu}&chatMode=${chatMode}&appId=${appId}`;
  938. if (appId) {
  939. navigate({
  940. pathname: '/knowledgeChat',
  941. search: search,
  942. })
  943. }
  944. } else {
  945. navigate({ pathname: '/newDeepseekChat' });
  946. }
  947. }}
  948. mode="inline"
  949. items={menuList}
  950. />
  951. <Modal
  952. title="重命名"
  953. open={modalOpen}
  954. width={300}
  955. maskClosable={false}
  956. onOk={() => {
  957. form.validateFields().then(async (values) => {
  958. setModalOpen(false);
  959. try {
  960. if (getType() === 'bigModel') {
  961. if (chatStore.chatMode === 'LOCAL') {
  962. await api.put(`/deepseek/api/dialog/update`, {
  963. id: values.dialogId,
  964. dialogName: values.dialogName
  965. });
  966. await fetchChatList(chatStore.chatMode);
  967. } else {
  968. await api.put(`/bigmodel/api/dialog/update`, {
  969. id: values.dialogId,
  970. dialogName: values.dialogName
  971. });
  972. await fetchChatList();
  973. }
  974. } else {
  975. await api.put(`/bigmodel/api/dialog/update`, {
  976. id: values.dialogId,
  977. dialogName: values.dialogName
  978. });
  979. await fetchChatList();
  980. }
  981. chatStore.updateCurrentSession((value) => {
  982. value.topic = values.dialogName;
  983. });
  984. } catch (error) {
  985. console.error(error);
  986. }
  987. }).catch((error) => {
  988. console.error(error);
  989. });
  990. }}
  991. onCancel={() => {
  992. setModalOpen(false);
  993. }}
  994. >
  995. <Form form={form} layout='inline'>
  996. <FormItem name='dialogId' noStyle />
  997. <FormItem
  998. label='名称'
  999. name='dialogName'
  1000. rules={[{ required: true, message: '名称不能为空', whitespace: true }]}
  1001. >
  1002. <Input
  1003. style={{ width: 300 }}
  1004. placeholder='请输入'
  1005. maxLength={20}
  1006. />
  1007. </FormItem>
  1008. </Form>
  1009. </Modal>
  1010. </SideBarContainer>
  1011. }
  1012. </>
  1013. );
  1014. }