refactor(iod): 重构数联网相关组件和逻辑
-优化了 Data、Scene 和 Team组件的逻辑,使用 currentIodMessage 替代复杂的条件判断- 改进了 IodRelevant 组件的动画和数据处理方式 - 调整了 Message 组件以支持数联网搜索功能 - 重构了 PlaygroundIodProvider,简化了上下文类型和数据处理 - 更新了数据库相关操作,使用新的 HistoryMessage 类型 - 新增了 IodDb 类来管理数联网连接配置
This commit is contained in:
@@ -11,8 +11,7 @@ const defaultData: IodRegistryEntry[] = [
|
||||
{
|
||||
name: "固态电池固体电解质材料数据集",
|
||||
doId: "CSTR:16666.11.nbsdc.9bjqrscd",
|
||||
description:
|
||||
"国家基础学科公共科学数据中心"
|
||||
description: "国家基础学科公共科学数据中心"
|
||||
},
|
||||
{
|
||||
name: "固体颗粒物与流体耦合",
|
||||
@@ -98,31 +97,22 @@ type Props = {
|
||||
className?: string
|
||||
}
|
||||
export const PlaygroundData: React.FC<Props> = ({ className }) => {
|
||||
const { messages, iodLoading, currentMessageId, iodSearch } =
|
||||
useMessageOption()
|
||||
const { iodLoading } = useMessageOption()
|
||||
|
||||
const { setShowPlayground, setDetailHeader, setDetailMain } =
|
||||
useIodPlaygroundContext()
|
||||
const {
|
||||
setShowPlayground,
|
||||
setDetailHeader,
|
||||
setDetailMain,
|
||||
currentIodMessage
|
||||
} = useIodPlaygroundContext()
|
||||
|
||||
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||
// 确保loading状态时数据大于3
|
||||
if (iodLoading) {
|
||||
return defaultData
|
||||
}
|
||||
|
||||
if (messages.length && iodSearch) {
|
||||
const currentMessage = messages?.find(
|
||||
(message) => message.id === currentMessageId
|
||||
)
|
||||
return currentMessage?.iodSources.data.data ?? []
|
||||
}
|
||||
|
||||
return defaultData
|
||||
}, [currentMessageId, messages, iodLoading, iodSearch])
|
||||
return currentIodMessage ? currentIodMessage.data?.data ?? [] : defaultData
|
||||
}, [currentIodMessage])
|
||||
|
||||
const title = useMemo(() => {
|
||||
return messages.length > 0 ? "推荐数据" : "热点数据"
|
||||
}, [messages])
|
||||
return currentIodMessage ? "推荐数据" : "热点数据"
|
||||
}, [currentIodMessage])
|
||||
|
||||
const showMore = () => {
|
||||
setShowPlayground(false)
|
||||
@@ -133,7 +123,7 @@ export const PlaygroundData: React.FC<Props> = ({ className }) => {
|
||||
onClick={() => setShowPlayground(false)}
|
||||
/>
|
||||
)
|
||||
setDetailMain(<Main loading={iodLoading} data={data} truncate={false} />)
|
||||
setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -142,7 +132,7 @@ export const PlaygroundData: React.FC<Props> = ({ className }) => {
|
||||
{/* 数据导航 */}
|
||||
<Header title={title} onClick={showMore} />
|
||||
{/* 数据列表 */}
|
||||
<Main loading={iodLoading} data={data.slice(0, 3)} />
|
||||
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ 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"
|
||||
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||
|
||||
const rotate = keyframes`
|
||||
0% {
|
||||
@@ -32,8 +33,8 @@ const breathe = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
// 花瓣
|
||||
const CircleElement = styled.div<{ delay: number; playing: boolean }>`
|
||||
// 花瓣 /* ${(props) => (props.playing ? "running" : "paused")}; */
|
||||
const CircleElement = styled.div<{ delay: number }>`
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 160px;
|
||||
@@ -46,7 +47,7 @@ const CircleElement = styled.div<{ delay: number; playing: boolean }>`
|
||||
${rotate} 6s linear infinite,
|
||||
${breathe} 2s infinite alternate;
|
||||
animation-delay: ${(props) => props.delay}s;
|
||||
animation-play-state: ${(props) => (props.playing ? "running" : "paused")};
|
||||
animation-play-state: running;
|
||||
animation-duration: 3s; /* 添加动画总持续时间 */
|
||||
animation-fill-mode: forwards; /* 保持动画结束时的状态 */
|
||||
`
|
||||
@@ -221,24 +222,19 @@ type Props = {
|
||||
className?: string
|
||||
}
|
||||
export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
const { messages, iodLoading, currentMessageId, iodSearch } =
|
||||
useMessageOption()
|
||||
const { iodLoading, iodSearch } = useMessageOption()
|
||||
|
||||
const { currentIodMessage } = useIodPlaygroundContext()
|
||||
|
||||
const showSearchData = useMemo(() => {
|
||||
return iodSearch && messages.length > 0 && !iodLoading
|
||||
}, [iodSearch, messages, iodLoading])
|
||||
|
||||
|
||||
return currentIodMessage && !iodLoading
|
||||
}, [currentIodMessage, iodLoading])
|
||||
|
||||
const data = useMemo(() => {
|
||||
const currentMessage = messages?.find(
|
||||
(message) => message.id === currentMessageId
|
||||
)
|
||||
|
||||
const loading = (iodSearch && iodLoading)
|
||||
const text = loading ? '正' : '已'
|
||||
const text2 = loading ? '进行' : '完成'
|
||||
const text3 = loading ? '……' : ''
|
||||
const loading = iodSearch && iodLoading
|
||||
const text = loading ? "正" : "已"
|
||||
const text2 = loading ? "进行" : "完成"
|
||||
const text3 = loading ? "……" : ""
|
||||
const duration = loading ? 2.5 : 0
|
||||
|
||||
return [
|
||||
@@ -256,7 +252,12 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
</span>
|
||||
高等院校的
|
||||
<span className="text-[#f00000]">
|
||||
<CountUp decimals={1} end={53.7} duration={duration} separator="," />
|
||||
<CountUp
|
||||
decimals={1}
|
||||
end={53.7}
|
||||
duration={duration}
|
||||
separator=","
|
||||
/>
|
||||
万个
|
||||
</span>
|
||||
科学数据集中{text2}搜索{text3}
|
||||
@@ -268,7 +269,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<span className="text-green-700">
|
||||
{" "}
|
||||
<CountUp
|
||||
end={currentMessage?.iodSources.data.total ?? 0}
|
||||
end={currentIodMessage?.data.total ?? 0}
|
||||
duration={2.5}
|
||||
separator=","
|
||||
/>
|
||||
@@ -288,9 +289,14 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<CountUp end={138} duration={duration} separator="," />
|
||||
万篇
|
||||
</span>
|
||||
学术论文、
|
||||
数据论文、
|
||||
<span className="text-[#f00000]">
|
||||
<CountUp end={18.3} decimals={1} duration={duration} separator="," />
|
||||
<CountUp
|
||||
end={18.3}
|
||||
decimals={1}
|
||||
duration={duration}
|
||||
separator=","
|
||||
/>
|
||||
万个
|
||||
</span>
|
||||
数据项目中{text2}搜索{text3}
|
||||
@@ -302,7 +308,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<span className="text-green-700">
|
||||
{" "}
|
||||
<CountUp
|
||||
end={currentMessage?.iodSources.scenario.total ?? 0}
|
||||
end={currentIodMessage?.scenario.total ?? 0}
|
||||
duration={2.5}
|
||||
separator=","
|
||||
/>
|
||||
@@ -324,7 +330,13 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
高等院校和科研机构、
|
||||
<span className="text-[#f00000]">
|
||||
{" "}
|
||||
<CountUp end={2.1} decimals={1} duration={duration} separator="," />万
|
||||
<CountUp
|
||||
end={2.1}
|
||||
decimals={1}
|
||||
duration={duration}
|
||||
separator=","
|
||||
/>
|
||||
万
|
||||
</span>
|
||||
家科技型企业、
|
||||
<span className="text-[#f00000]">
|
||||
@@ -340,7 +352,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<span className="text-green-700">
|
||||
{" "}
|
||||
<CountUp
|
||||
end={currentMessage?.iodSources.organization.total ?? 0}
|
||||
end={currentIodMessage?.organization.total ?? 0}
|
||||
duration={2.5}
|
||||
separator=","
|
||||
/>
|
||||
@@ -353,7 +365,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
)
|
||||
}
|
||||
]
|
||||
}, [messages, iodLoading])
|
||||
}, [showSearchData, iodLoading])
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -365,9 +377,9 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<div
|
||||
className={`absolute inset-0 pointer-events-none z-0 overflow-hidden ${showSearchData ? "" : ""}`}>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64">
|
||||
<CircleElement delay={0} playing={true} />
|
||||
<CircleElement delay={1} playing={true} />
|
||||
<CircleElement delay={2} playing={true} />
|
||||
<CircleElement delay={0} />
|
||||
<CircleElement delay={1} />
|
||||
<CircleElement delay={2} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -376,16 +388,14 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
<h2 className="text-xl font-semibold text-[#1a3c87] flex justify-center items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<SearchIcon />
|
||||
{messages.length > 0
|
||||
? "科创数联网深度搜索"
|
||||
: "科创数联网连接资源"}
|
||||
{currentIodMessage ? "科创数联网深度搜索" : "科创数联网连接资源"}
|
||||
</div>
|
||||
{/*<button className="bg-[#2563eb1a] text-[#08307f] font-medium py-1 px-3 rounded-full text-sm hover:bg-[#2563eb1a] transition-colors float-right">*/}
|
||||
{/* {data.length}个结果*/}
|
||||
{/*</button>*/}
|
||||
</h2>
|
||||
<p className="text-sm text-[#1a3c87] mt-1 text-center">
|
||||
{messages.length > 0
|
||||
{currentIodMessage
|
||||
? "下面是在科创数联网上进行深度搜索得到的相关数据、场景和团队"
|
||||
: "下面是科创数联网连接的数据、场景和团队"}
|
||||
</p>
|
||||
@@ -393,7 +403,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-2 flex-1 overflow-y-auto">
|
||||
{messages.length ? (
|
||||
{currentIodMessage ? (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key="search-results"
|
||||
@@ -422,16 +432,16 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
<div
|
||||
className={`text-gray-700 ${showSearchData ? "text-sm" : "text-lg"}`}>
|
||||
{item.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{item.description && (
|
||||
<div className="flex-1">
|
||||
<p className="text-xs text-gray-500 mt-1 pl-7">
|
||||
<div className="text-xs text-gray-500 mt-1 pl-7">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -25,8 +25,10 @@ import { GenerationInfo } from "./GenerationInfo"
|
||||
import { parseReasoning } from "@/libs/reasoning"
|
||||
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
|
||||
import { AllIodRegistryEntry } from "@/types/iod.ts"
|
||||
import { PiNetwork } from "react-icons/pi"
|
||||
|
||||
type Props = {
|
||||
id?: string
|
||||
message: string
|
||||
message_type?: string
|
||||
hideCopy?: boolean
|
||||
@@ -50,9 +52,11 @@ type Props = {
|
||||
generationInfo?: any
|
||||
isStreaming: boolean
|
||||
reasoningTimeTaken?: number
|
||||
iodSearch?: boolean
|
||||
setCurrentMessageId: (id: string) => void
|
||||
}
|
||||
|
||||
export const PlaygroundMessage = (props: Props) => {
|
||||
export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
const [isBtnPressed, setIsBtnPressed] = React.useState(false)
|
||||
const [editMode, setEditMode] = React.useState(false)
|
||||
|
||||
@@ -258,6 +262,18 @@ export const PlaygroundMessage = (props: Props) => {
|
||||
)}
|
||||
{props.isBot && (
|
||||
<>
|
||||
{/*数联网搜索*/}
|
||||
{props.iodSearch && (
|
||||
<Tooltip title="数联网信息">
|
||||
<button
|
||||
onClick={() => props.setCurrentMessageId(props.id)}
|
||||
aria-label="数联网信息"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
<PiNetwork className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{!props.hideCopy && (
|
||||
<Tooltip title={t("copyToClipboard")}>
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import React, { useMemo } from "react"
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Drawer, Skeleton } from "antd"
|
||||
import { Card, Skeleton } from "antd"
|
||||
import { IodRegistryEntry } from "@/types/iod.ts"
|
||||
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||
|
||||
@@ -33,7 +33,11 @@ type HeaderProps = {
|
||||
showButton?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
const Header: React.FC<HeaderProps> = ({ title, showButton = true, onClick }) => (
|
||||
const Header: React.FC<HeaderProps> = ({
|
||||
title,
|
||||
showButton = true,
|
||||
onClick
|
||||
}) => (
|
||||
<DataNavigation
|
||||
Header={
|
||||
<div className="flex items-center text-[#4ab01a] gap-1">
|
||||
@@ -97,36 +101,28 @@ const Main: React.FC<MainProps> = ({ data, loading, truncate = true }) => (
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
}
|
||||
export const PlaygroundScene: React.FC<Props> = ({ className }) => {
|
||||
const { messages, iodLoading, currentMessageId, iodSearch } =
|
||||
useMessageOption()
|
||||
const { iodLoading } = useMessageOption()
|
||||
|
||||
const { setShowPlayground, setDetailHeader, setDetailMain } =
|
||||
useIodPlaygroundContext()
|
||||
const {
|
||||
setShowPlayground,
|
||||
setDetailHeader,
|
||||
setDetailMain,
|
||||
currentIodMessage
|
||||
} = useIodPlaygroundContext()
|
||||
|
||||
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||
// 确保loading状态时数据大于3
|
||||
if (iodLoading) {
|
||||
return defaultData
|
||||
}
|
||||
|
||||
if (messages.length && iodSearch) {
|
||||
const currentMessage = messages?.find(
|
||||
(message) => message.id === currentMessageId
|
||||
)
|
||||
return currentMessage?.iodSources.scenario.data ?? []
|
||||
}
|
||||
|
||||
return defaultData
|
||||
}, [currentMessageId, messages, iodLoading])
|
||||
return currentIodMessage
|
||||
? currentIodMessage.scenario?.data ?? []
|
||||
: defaultData
|
||||
}, [currentIodMessage])
|
||||
|
||||
const title = useMemo(() => {
|
||||
return messages.length > 0 ? "推荐场景" : "热点场景"
|
||||
}, [messages])
|
||||
return currentIodMessage ? "推荐场景" : "热点场景"
|
||||
}, [currentIodMessage])
|
||||
|
||||
const showMore = () => {
|
||||
setShowPlayground(false)
|
||||
@@ -137,19 +133,17 @@ export const PlaygroundScene: React.FC<Props> = ({ className }) => {
|
||||
onClick={() => setShowPlayground(false)}
|
||||
/>
|
||||
)
|
||||
setDetailMain(<Main loading={iodLoading} data={data} truncate={false} />)
|
||||
setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`${className}`}
|
||||
hoverable>
|
||||
<Card className={`${className}`} hoverable>
|
||||
<div className="h-full flex flex-col gap-2 relative">
|
||||
{/* 数据导航 */}
|
||||
<Header title={title} onClick={showMore} />
|
||||
|
||||
{/* 数据列表 */}
|
||||
<Main loading={iodLoading} data={data.slice(0, 3)} />
|
||||
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from "react"
|
||||
import React, { useMemo } from "react"
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Skeleton } from "antd"
|
||||
import { IodRegistryEntry } from "@/types/iod.ts"
|
||||
@@ -17,11 +17,10 @@ const defaultData: IodRegistryEntry[] = [
|
||||
doId: "91320507MAEKWL5Y2L"
|
||||
},
|
||||
{
|
||||
|
||||
name: "伊利诺伊大学香槟分校(UIUC)",
|
||||
description: "创建于1867年,坐落于伊利诺伊州双子城厄巴纳–香槟市,",
|
||||
doId: "bdware.org/uiuc",
|
||||
},
|
||||
doId: "bdware.org/uiuc"
|
||||
}
|
||||
]
|
||||
|
||||
type HeaderProps = {
|
||||
@@ -113,31 +112,24 @@ type Props = {
|
||||
className?: string
|
||||
}
|
||||
export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
|
||||
const { messages, iodLoading, currentMessageId, iodSearch } =
|
||||
useMessageOption()
|
||||
const { iodLoading } = useMessageOption()
|
||||
|
||||
const { setShowPlayground, setDetailHeader, setDetailMain } =
|
||||
useIodPlaygroundContext()
|
||||
const {
|
||||
setShowPlayground,
|
||||
setDetailHeader,
|
||||
setDetailMain,
|
||||
currentIodMessage
|
||||
} = useIodPlaygroundContext()
|
||||
|
||||
const data = useMemo<IodRegistryEntry[]>(() => {
|
||||
// 确保loading状态时数据大于3
|
||||
if (iodLoading) {
|
||||
return defaultData
|
||||
}
|
||||
|
||||
if (messages.length && iodSearch) {
|
||||
const currentMessage = messages?.find(
|
||||
(message) => message.id === currentMessageId
|
||||
)
|
||||
return currentMessage?.iodSources.organization.data ?? []
|
||||
}
|
||||
|
||||
return defaultData
|
||||
}, [currentMessageId, messages, iodLoading])
|
||||
return currentIodMessage
|
||||
? currentIodMessage.organization?.data ?? []
|
||||
: defaultData
|
||||
}, [currentIodMessage])
|
||||
|
||||
const title = useMemo(() => {
|
||||
return messages.length > 0 ? "推荐团队" : "热点团队"
|
||||
}, [messages])
|
||||
return currentIodMessage ? "推荐团队" : "热点团队"
|
||||
}, [currentIodMessage])
|
||||
|
||||
const showMore = () => {
|
||||
setShowPlayground(false)
|
||||
@@ -148,17 +140,18 @@ export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
|
||||
onClick={() => setShowPlayground(false)}
|
||||
/>
|
||||
)
|
||||
setDetailMain(<Main loading={iodLoading} data={data} truncate={false} flat={false} />)
|
||||
setDetailMain(
|
||||
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} flat={false} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Card className={`${className}`} hoverable>
|
||||
<div className="h-full flex flex-col gap-2 relative">
|
||||
{/* 数据导航 */}
|
||||
<Header title={title} onClick={showMore} />
|
||||
{/* 数据列表 */}
|
||||
<Main loading={iodLoading} data={data.slice(0, 3)} />
|
||||
<Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { Form, Image, Input, Modal, Tooltip, message } from "antd"
|
||||
import { Share } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import type { Message } from "~/store/option"
|
||||
import Markdown from "./Markdown"
|
||||
import { Form, Image, Input, message, Modal } from "antd"
|
||||
import React from "react"
|
||||
import Markdown from "./Markdown"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { getPageShareUrl } from "~/services/ollama"
|
||||
import { cleanUrl } from "~/libs/clean-url"
|
||||
import { getTitleById, getUserId, saveWebshare } from "@/db"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import fetcher from "@/libs/fetcher"
|
||||
import { Message } from "@/types/message.ts"
|
||||
|
||||
type Props = {
|
||||
messages: Message[]
|
||||
|
||||
Reference in New Issue
Block a user