diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..e461fa2 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index 5f6dec1..86e5bc0 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "cheerio": "^1.0.0-rc.12", "d3-dsv": "2", "dayjs": "^1.11.10", + "framer-motion": "^12.23.12", "html-to-text": "^9.0.5", "i18next": "^23.10.1", "i18next-browser-languagedetector": "^7.2.0", @@ -47,6 +48,7 @@ "property-information": "^6.4.1", "pubsub-js": "^1.9.4", "react": "18.2.0", + "react-countup": "^6.5.3", "react-dom": "18.2.0", "react-i18next": "^14.1.0", "react-icons": "^5.2.1", diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..d71b8ca Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/Common/Playground/Data.tsx b/src/components/Common/Playground/Data.tsx index 3838d97..e04084f 100644 --- a/src/components/Common/Playground/Data.tsx +++ b/src/components/Common/Playground/Data.tsx @@ -4,6 +4,8 @@ import { Card, Drawer, Skeleton } from "antd" import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { IodRegistryEntry } from "@/types/iod.ts" +// import { Drawer } from './Drawer.tsx' + const defaultData: IodRegistryEntry[] = [ { name: "2019-2024年黄海清浅海域中河湖代数生物物种数据集", @@ -66,7 +68,8 @@ const ShowCard: React.FC = ({ ) export const PlaygroundData = () => { - const { messages, iodLoading, currentMessageId, iodSearch } = useMessageOption() + const { messages, iodLoading, currentMessageId, iodSearch } = + useMessageOption() const data = useMemo(() => { // 确保loading状态时数据大于3 @@ -147,7 +150,12 @@ export const PlaygroundData = () => { width="33.33%">
{data.map((item, index) => ( - + ))}
diff --git a/src/components/Common/Playground/Drawer.tsx b/src/components/Common/Playground/Drawer.tsx new file mode 100644 index 0000000..fdcf26a --- /dev/null +++ b/src/components/Common/Playground/Drawer.tsx @@ -0,0 +1,91 @@ +// Drawer.tsx +import React, { useEffect } from "react" +import styled from "styled-components" +import { shadow } from "pdfjs-dist" + +interface DrawerProps { + open: boolean + onClose: () => void + children: React.ReactNode + width?: string | number + overlay?: boolean + keydown?: boolean + shadow?: boolean +} + +const DrawerOverlay = styled.div<{ isOpen: boolean }>` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; + visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; + transition: + opacity 0.3s ease, + visibility 0.3s ease; + z-index: 1000; +` + +const DrawerContainer = styled.div<{ + isOpen: boolean + width: string | number + shadow: boolean +}>` + position: fixed; + top: 0; + right: 0; + height: 100%; + width: ${({ width }) => (typeof width === "number" ? `${width}px` : width)}; + background: #ffffff; + box-shadow: ${shadow ? "-2px 0 8px rgba(0, 0, 0, 0.15)" : ""}; + transform: translateX(${({ isOpen }) => (isOpen ? "0" : "100%")}); + transition: transform 0.3s ease; + z-index: 9999; + overflow-y: auto; +` + +export const Drawer: React.FC = ({ + open, + onClose, + children, + overlay = true, + keydown = true, + shadow = true, + width = "300px" +}) => { + // 处理 Escape 键关闭抽屉 + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && open) { + onClose() + } + } + if (keydown) { + document.addEventListener("keydown", handleEscape) + } + + return () => { + if (keydown) { + document.removeEventListener("keydown", handleEscape) + } + } + }, [open, onClose, keydown]) + + // 处理点击遮罩层关闭抽屉 + const handleOverlayClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose() + } + } + + return ( + <> + {overlay && } + + {children} + + + ) +} diff --git a/src/components/Common/Playground/History.tsx b/src/components/Common/Playground/History.tsx index 7f3d25a..8d0a306 100644 --- a/src/components/Common/Playground/History.tsx +++ b/src/components/Common/Playground/History.tsx @@ -22,6 +22,8 @@ import { PlusOutlined, RightOutlined } from "@ant-design/icons" import { qaPrompt } from "@/libs/playground.tsx" import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx" import { fetchChatModels } from "@/services/ollama.ts" +import logo from '@/assets/logo.png' + const ModelIcon = () => { return ( @@ -115,6 +117,7 @@ export const PlaygroundHistory = () => { {/*Header*/}
+ logo

数联网科创智能体

diff --git a/src/components/Common/Playground/IodRelevant.tsx b/src/components/Common/Playground/IodRelevant.tsx index b47ba9e..550dc70 100644 --- a/src/components/Common/Playground/IodRelevant.tsx +++ b/src/components/Common/Playground/IodRelevant.tsx @@ -1,8 +1,15 @@ import React, { useMemo } from "react" -import { Card } from "antd" - -// 使用 CSS-in-JS 方式 +import { Avatar, Card } from "antd" +import { AnimatePresence, motion } from "framer-motion" // 使用 CSS-in-JS 方式 import styled, { keyframes } from "styled-components" +import CountUp from "react-countup" +import { TalentPoolIcon } from "@/components/Icons/TalentPool .tsx" +import { ResearchPaperIcon } from "@/components/Icons/ResearchPaper.tsx" +import { DataProjectIcon } from "@/components/Icons/DataProject.tsx" +import { DatasetIcon } from "@/components/Icons/Dataset.tsx" +import { TechCompanyIcon } from "@/components/Icons/TechCompany.tsx" +import { ResearchInstitutesIcon } from "@/components/Icons/ResearchInstitutes.tsx" +import { NSDCIcon } from "@/components/Icons/NSDC.tsx" const rotate = keyframes` 0% { @@ -25,6 +32,7 @@ const breathe = keyframes` } ` +// 花瓣 const CircleElement = styled.div<{ delay: number; playing: boolean }>` position: absolute; width: 300px; @@ -39,15 +47,29 @@ const CircleElement = styled.div<{ delay: number; playing: boolean }>` ${breathe} 2s infinite alternate; animation-delay: ${(props) => props.delay}s; animation-play-state: ${(props) => (props.playing ? "running" : "paused")}; + animation-duration: 3s; /* 添加动画总持续时间 */ + animation-fill-mode: forwards; /* 保持动画结束时的状态 */ ` -const SuccessIcon = () => { +const FrostedGlassCard = styled(Card)` + background: rgba(255, 255, 255, 0.25) !important; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.18); +` + +const SuccessIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { return ( + className="text-green-500" + ref={ref} + {...props}> { /> ) -} +}) -const LoadingIcon = () => { +const LoadingIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { return ( { version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="29588" - width="18" - height="18"> + ref={ref} + {...props}> ) -} - +}) const SearchIcon = () => { return ( { ) } +// 自定义统计卡片组件 +const StatCard: React.FC<{ + number: number + unit?: string + label: string + decimals?: number + icon: React.ReactNode +}> = ({ number, unit, label, decimals, icon }) => { + return ( +
+ + +
+ + {unit} +
+
+ {" "} + {label} +
+
+ ) +} + +// 主组件 +export const StatisticGrid: React.FC = () => { + return ( +
+ {/* 第一行:3 个卡片 */} +
+ } + number={11} + unit="家" + label="国家科学数据中心" + /> + + } + number={763} + unit="家" + label="高等院校和科研机构" + /> + + } + number={2.1} + decimals={1} + unit="万" + label="科技型企业" + /> +
+ + {/* 第二行:4 个卡片 */} +
+ } + number={537163} + label="数据集" + /> + + } + number={183729} + label="数据项目" + /> + + } + number={1380026} + label="科研论文" + /> + + } + number={2} + unit="万" + label="科创人才" + /> +
+
+ ) +} + export const PlaygroundIodRelevant: React.FC = () => { const { messages, iodLoading, currentMessageId, iodSearch } = useMessageOption() - const showDescription = useMemo(() => { + const showSearchData = useMemo(() => { return iodSearch && messages.length > 0 && !iodLoading }, [iodSearch, messages, iodLoading]) @@ -111,17 +234,34 @@ export const PlaygroundIodRelevant: React.FC = () => { { title: (

- 已连接 11家 - 国家科学数据中心的 - 500000+个 科学数据集中 + 已在 + + 个 + + 国家和省部级科学数据中心、 + + {" "} + 所 + + 高等院校的 + + + 万个 + + 科学数据集中进行搜索

), - description: showDescription ? ( + description: showSearchData ? (

已发现 {" "} - {currentMessage?.iodSources.data.total}个{" "} + + 个{" "} 数据集

@@ -132,18 +272,32 @@ export const PlaygroundIodRelevant: React.FC = () => { { title: (

- 已连接 1000000+篇 论文和 - 50000+个 科创场景 + 已在 + + + 万篇 + + 学术论文、 + + + 万个 + + 数据项目中进行搜索

), - description: showDescription ? ( + description: showSearchData ? (

已发现 {" "} - {currentMessage?.iodSources.scenario.total}个{" "} + + 个{" "} - 科创场景 + 场景

) : ( "" @@ -152,19 +306,36 @@ export const PlaygroundIodRelevant: React.FC = () => { { title: (

- 已连接 1000+位 智库专家 - 763家 高校和科研机构和 - 21000+家 科技企业 + 正在 + + 家 + + 高等院校和科研机构、 + + {" "} + 万 + + 家科技型企业、 + + {" "} + 万 + + 科技人才中进行搜索

), - description: showDescription ? ( + description: showSearchData ? (

已发现 {" "} - {currentMessage?.iodSources.organization.total}个{" "} + + 个{" "} - 科技企业 + 组织

) : ( "" @@ -180,7 +351,7 @@ export const PlaygroundIodRelevant: React.FC = () => { className="flex flex-col h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] translate-y-[-2px] !bg-[#f0f9ff]">
{/* 花瓣效果 */} -
+
@@ -200,41 +371,71 @@ export const PlaygroundIodRelevant: React.FC = () => { {/**/}

- 下面是在数联网上进行深度搜索的相关内容 + 下面是在数联网上进行深度搜索得到的科创相关数据、场景和团队

{/* Content */}
- {data.map((item, index) => ( - -
-
-
- {iodSearch && iodLoading ? ( - - ) : ( - - )} -
-

- {item.title} -

-
- {item.description && ( -
-

- {item.description} -

-
- )} -
-
- ))} + {showSearchData ? ( + + + {data.map((item, index) => ( + +
+
+
+ {iodSearch && iodLoading ? ( + + ) : ( + + )} +
+

+ {item.title} +

+
+ {item.description && ( +
+

+ {item.description} +

+
+ )} +
+
+ ))} +
+
+ ) : ( + + + + + + )}
diff --git a/src/components/Icons/DataProject.tsx b/src/components/Icons/DataProject.tsx new file mode 100644 index 0000000..a70c43b --- /dev/null +++ b/src/components/Icons/DataProject.tsx @@ -0,0 +1,24 @@ +import React from "react" + +export const DataProjectIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + + ) +}) diff --git a/src/components/Icons/Dataset.tsx b/src/components/Icons/Dataset.tsx new file mode 100644 index 0000000..a0ead9d --- /dev/null +++ b/src/components/Icons/Dataset.tsx @@ -0,0 +1,27 @@ +import React from "react" + +export const DatasetIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + + + ) +}) diff --git a/src/components/Icons/NSDC.tsx b/src/components/Icons/NSDC.tsx new file mode 100644 index 0000000..4c3bba0 --- /dev/null +++ b/src/components/Icons/NSDC.tsx @@ -0,0 +1,26 @@ +import React from "react" + +export const NSDCIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + + ) +}) diff --git a/src/components/Icons/ResearchInstitutes.tsx b/src/components/Icons/ResearchInstitutes.tsx new file mode 100644 index 0000000..6a28a5e --- /dev/null +++ b/src/components/Icons/ResearchInstitutes.tsx @@ -0,0 +1,24 @@ +import React from "react" + +export const ResearchInstitutesIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + + ) +}) diff --git a/src/components/Icons/ResearchPaper.tsx b/src/components/Icons/ResearchPaper.tsx new file mode 100644 index 0000000..23b37a5 --- /dev/null +++ b/src/components/Icons/ResearchPaper.tsx @@ -0,0 +1,21 @@ +import React from "react" + +export const ResearchPaperIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + ) +}) diff --git a/src/components/Icons/TalentPool .tsx b/src/components/Icons/TalentPool .tsx new file mode 100644 index 0000000..c3f6c8b --- /dev/null +++ b/src/components/Icons/TalentPool .tsx @@ -0,0 +1,24 @@ +import React from "react" + +export const TalentPoolIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + + ) +}) diff --git a/src/components/Icons/TechCompany.tsx b/src/components/Icons/TechCompany.tsx new file mode 100644 index 0000000..f664f5d --- /dev/null +++ b/src/components/Icons/TechCompany.tsx @@ -0,0 +1,22 @@ +import React from "react" + +export const TechCompanyIcon = React.forwardRef< + SVGSVGElement, + React.SVGProps +>((props, ref) => { + return ( + + + + ) +}) diff --git a/src/components/Layouts/Header.tsx b/src/components/Layouts/Header.tsx index bef3968..22c1e80 100644 --- a/src/components/Layouts/Header.tsx +++ b/src/components/Layouts/Header.tsx @@ -6,6 +6,7 @@ import { PlusOutlined } from "@ant-design/icons" import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useTranslation } from "react-i18next" import { NavLink, useLocation } from "react-router-dom" +import logo from "@/assets/logo.png" interface SettingIconProps {} const SettingIcon: React.FC = () => { @@ -117,8 +118,9 @@ export const Header: React.FC = ({ setOpenModelSettings }) => { fill="#ffffff" p-id="9635"> -

- 数联网科创智能体 +

+ logo +

数联网科创智能体

{/*设置框*/} diff --git a/src/components/Option/Playground/Playground.tsx b/src/components/Option/Playground/Playground.tsx index fe017df..e81a26b 100644 --- a/src/components/Option/Playground/Playground.tsx +++ b/src/components/Option/Playground/Playground.tsx @@ -1,13 +1,9 @@ -import React, { useContext } from "react" - -import { Card } from "antd" +import React from "react" import { PlaygroundForm } from "./PlaygroundForm" import { PlaygroundChat } from "./PlaygroundChat" import { useMessageOption } from "@/hooks/useMessageOption" import { webUIResumeLastChat } from "@/services/app" -import { PlaygroundData } from "@/components/Common/Playground/Data.tsx" -import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx" import { formatToChatHistory, @@ -19,9 +15,8 @@ import { getLastUsedChatSystemPrompt } from "@/services/model-settings" import { useStoreChatModelSettings } from "@/store/model" import { useSmartScroll } from "@/hooks/useSmartScroll" import { ChevronDown } from "lucide-react" -import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx" import { PlaygroundHistory } from "@/components/Common/Playground/History.tsx" -import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx" +import { PlaygroundIod } from "@/components/Option/Playground/PlaygroundIod.tsx" export const Playground = () => { const drop = React.useRef(null) @@ -165,21 +160,7 @@ export const Playground = () => {
- {/*auto_530px_165px*/} -
-
- -
-
- - -
-
- -
-
+
) } diff --git a/src/components/Option/Playground/PlaygroundEmpty.tsx b/src/components/Option/Playground/PlaygroundEmpty.tsx index c220e59..366862c 100644 --- a/src/components/Option/Playground/PlaygroundEmpty.tsx +++ b/src/components/Option/Playground/PlaygroundEmpty.tsx @@ -19,20 +19,20 @@ export const PlaygroundEmpty = () => { }) function handleQuestion(message: string) { - void sendMessage({ message, image: "", isRegenerate: true }) + void sendMessage({ message, image: "" }) } return (
{/* 标题区域 */} -
-

- 数联网科创智能体 -

-

您好!请问有什么可以帮您?

-
+ {/*
*/} + {/* */} + {/* 数联网科创智能体*/} + {/* */} + {/*

您好!请问有什么可以帮您?

*/} + {/*
*/} {/* 卡片网格布局 */} diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx index fea783a..0f7c098 100644 --- a/src/components/Option/Playground/PlaygroundForm.tsx +++ b/src/components/Option/Playground/PlaygroundForm.tsx @@ -1,10 +1,18 @@ import { useForm } from "@mantine/form" import { useMutation, useQueryClient } from "@tanstack/react-query" -import React from "react" +import React, { useMemo } from "react" import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize" import { toBase64 } from "~/libs/to-base64" import { useMessageOption } from "~/hooks/useMessageOption" -import { Checkbox, Dropdown, Image, Switch, Tooltip } from "antd" +import { + Button, + Checkbox, + Dropdown, + Image, + MenuProps, + Switch, + Tooltip +} from "antd" import { useWebUI } from "~/store/webui" import { defaultEmbeddingModelForRag } from "~/services/ollama" import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react" @@ -205,6 +213,41 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { } } + const iodSearchItems = useMemo(() => { + return [ + { + key: 0, + label: ( +

{ + setIodSearch(true) + }}> +

+ 开 +

+

输出带数联网的回答

+

+ ) + }, + { + key: 1, + label: ( +

{ + setIodSearch(false) + }}> +

+ 关闭 +

+

快速直接回答

+

+ ) + } + ] + }, [iodSearch]) + return (
@@ -318,21 +361,40 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { />
- -
- - setIodSearch(e)} - checkedChildren={t("form.webSearch.on")} - unCheckedChildren={t("form.webSearch.off")} - /> -
-
+ {/**/} + {/*
*/} + {/* */} + {/* setIodSearch(e)}*/} + {/* checkedChildren={t("form.webSearch.on")}*/} + {/* unCheckedChildren={t("form.webSearch.off")}*/} + {/* />*/} + {/*
*/} + {/**/} + + +
)}
diff --git a/src/components/Option/Playground/PlaygroundIod.tsx b/src/components/Option/Playground/PlaygroundIod.tsx new file mode 100644 index 0000000..948c37d --- /dev/null +++ b/src/components/Option/Playground/PlaygroundIod.tsx @@ -0,0 +1,25 @@ +import React from "react" + +import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx" +import { PlaygroundData } from "@/components/Common/Playground/Data.tsx" +import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx" +import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx" + +export const PlaygroundIod = () => { + return ( +
+
+ +
+
+ + +
+
+ +
+
+ ) +} diff --git a/src/web/web.ts b/src/web/web.ts index 3113a84..26eb38e 100644 --- a/src/web/web.ts +++ b/src/web/web.ts @@ -108,20 +108,23 @@ export const getSystemPromptForWeb = async ( // .join("\n") } for (const key of Object.keys(iodSearchResults)) { - const arr = iodSearchResults[key] - _iodSearchResults[key] = arr.data.map((res) => ({ - doId: res.doId, - name: res.name, - url: res.url, - data_type: res.data_type, - data_space: res.data_space, - content: res.content || res.description, - tokenCount: - res.content || res.description - ? calculateTokenCount(res.content || res.description) - : 0, - traceId: res?.traceId - })) + const record = iodSearchResults[key] + _iodSearchResults[key] = { + ...record, + data: record.data.map((res) => ({ + doId: res.doId, + name: res.name, + url: res.url, + data_type: res.data_type, + data_space: res.data_space, + content: res.content || res.description, + tokenCount: + res.content || res.description + ? calculateTokenCount(res.content || res.description) + : 0, + traceId: res?.traceId + })) + } } } finally { resolve(0) @@ -130,8 +133,8 @@ export const getSystemPromptForWeb = async ( ]) // let search_results_iod = "" - debugger let iod_search_results = Object.values(_iodSearchResults) + .map(item => item.data) .flat() .map((result, idx) => { const nameAttr = result.name ? ` name="${result.name}"` : ""