Compare commits

...

2 Commits

Author SHA1 Message Date
zhaoweijie
6f386709e2 Merge remote-tracking branch 'origin/main' 2025-08-24 19:00:58 +08:00
zhaoweijie
2b4885ae2d refactor(iod): 重构数联网相关组件和逻辑
-优化了 Data、Scene 和 Team组件的逻辑,使用 currentIodMessage 替代复杂的条件判断- 改进了 IodRelevant 组件的动画和数据处理方式
- 调整了 Message 组件以支持数联网搜索功能
- 重构了 PlaygroundIodProvider,简化了上下文类型和数据处理
- 更新了数据库相关操作,使用新的 HistoryMessage 类型
- 新增了 IodDb 类来管理数联网连接配置
2025-08-24 19:00:49 +08:00
20 changed files with 415 additions and 399 deletions

View File

@ -11,8 +11,7 @@ const defaultData: IodRegistryEntry[] = [
{ {
name: "固态电池固体电解质材料数据集", name: "固态电池固体电解质材料数据集",
doId: "CSTR:16666.11.nbsdc.9bjqrscd", doId: "CSTR:16666.11.nbsdc.9bjqrscd",
description: description: "国家基础学科公共科学数据中心"
"国家基础学科公共科学数据中心"
}, },
{ {
name: "固体颗粒物与流体耦合", name: "固体颗粒物与流体耦合",
@ -98,31 +97,22 @@ type Props = {
className?: string className?: string
} }
export const PlaygroundData: React.FC<Props> = ({ className }) => { export const PlaygroundData: React.FC<Props> = ({ className }) => {
const { messages, iodLoading, currentMessageId, iodSearch } = const { iodLoading } = useMessageOption()
useMessageOption()
const { setShowPlayground, setDetailHeader, setDetailMain } = const {
useIodPlaygroundContext() setShowPlayground,
setDetailHeader,
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const data = useMemo<IodRegistryEntry[]>(() => {
// 确保loading状态时数据大于3 return currentIodMessage ? currentIodMessage.data?.data ?? [] : defaultData
if (iodLoading) { }, [currentIodMessage])
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])
const title = useMemo(() => { const title = useMemo(() => {
return messages.length > 0 ? "推荐数据" : "热点数据" return currentIodMessage ? "推荐数据" : "热点数据"
}, [messages]) }, [currentIodMessage])
const showMore = () => { const showMore = () => {
setShowPlayground(false) setShowPlayground(false)
@ -133,7 +123,7 @@ export const PlaygroundData: React.FC<Props> = ({ className }) => {
onClick={() => setShowPlayground(false)} onClick={() => setShowPlayground(false)}
/> />
) )
setDetailMain(<Main loading={iodLoading} data={data} truncate={false} />) setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
} }
return ( return (
@ -142,7 +132,7 @@ export const PlaygroundData: React.FC<Props> = ({ className }) => {
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <Header title={title} onClick={showMore} />
{/* 数据列表 */} {/* 数据列表 */}
<Main loading={iodLoading} data={data.slice(0, 3)} /> <Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
</div> </div>
</Card> </Card>
) )

View File

@ -10,6 +10,7 @@ import { DatasetIcon } from "@/components/Icons/Dataset.tsx"
import { TechCompanyIcon } from "@/components/Icons/TechCompany.tsx" import { TechCompanyIcon } from "@/components/Icons/TechCompany.tsx"
import { ResearchInstitutesIcon } from "@/components/Icons/ResearchInstitutes.tsx" import { ResearchInstitutesIcon } from "@/components/Icons/ResearchInstitutes.tsx"
import { NSDCIcon } from "@/components/Icons/NSDC.tsx" import { NSDCIcon } from "@/components/Icons/NSDC.tsx"
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
const rotate = keyframes` const rotate = keyframes`
0% { 0% {
@ -32,8 +33,8 @@ const breathe = keyframes`
} }
` `
// 花瓣 // 花瓣 /* ${(props) => (props.playing ? "running" : "paused")}; */
const CircleElement = styled.div<{ delay: number; playing: boolean }>` const CircleElement = styled.div<{ delay: number }>`
position: absolute; position: absolute;
width: 300px; width: 300px;
height: 160px; height: 160px;
@ -46,7 +47,7 @@ const CircleElement = styled.div<{ delay: number; playing: boolean }>`
${rotate} 6s linear infinite, ${rotate} 6s linear infinite,
${breathe} 2s infinite alternate; ${breathe} 2s infinite alternate;
animation-delay: ${(props) => props.delay}s; animation-delay: ${(props) => props.delay}s;
animation-play-state: ${(props) => (props.playing ? "running" : "paused")}; animation-play-state: running;
animation-duration: 3s; /* 添加动画总持续时间 */ animation-duration: 3s; /* 添加动画总持续时间 */
animation-fill-mode: forwards; /* 保持动画结束时的状态 */ animation-fill-mode: forwards; /* 保持动画结束时的状态 */
` `
@ -221,24 +222,19 @@ type Props = {
className?: string className?: string
} }
export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => { export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
const { messages, iodLoading, currentMessageId, iodSearch } = const { iodLoading, iodSearch } = useMessageOption()
useMessageOption()
const { currentIodMessage } = useIodPlaygroundContext()
const showSearchData = useMemo(() => { const showSearchData = useMemo(() => {
return iodSearch && messages.length > 0 && !iodLoading return currentIodMessage && !iodLoading
}, [iodSearch, messages, iodLoading]) }, [currentIodMessage, iodLoading])
const data = useMemo(() => { const data = useMemo(() => {
const currentMessage = messages?.find( const loading = iodSearch && iodLoading
(message) => message.id === currentMessageId 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 const duration = loading ? 2.5 : 0
return [ return [
@ -256,7 +252,12 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
</span> </span>
<span className="text-[#f00000]"> <span className="text-[#f00000]">
<CountUp decimals={1} end={53.7} duration={duration} separator="," /> <CountUp
decimals={1}
end={53.7}
duration={duration}
separator=","
/>
</span> </span>
{text2}{text3} {text2}{text3}
@ -268,7 +269,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<span className="text-green-700"> <span className="text-green-700">
{" "} {" "}
<CountUp <CountUp
end={currentMessage?.iodSources.data.total ?? 0} end={currentIodMessage?.data.total ?? 0}
duration={2.5} duration={2.5}
separator="," separator=","
/> />
@ -288,9 +289,14 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<CountUp end={138} duration={duration} separator="," /> <CountUp end={138} duration={duration} separator="," />
</span> </span>
<span className="text-[#f00000]"> <span className="text-[#f00000]">
<CountUp end={18.3} decimals={1} duration={duration} separator="," /> <CountUp
end={18.3}
decimals={1}
duration={duration}
separator=","
/>
</span> </span>
{text2}{text3} {text2}{text3}
@ -302,7 +308,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<span className="text-green-700"> <span className="text-green-700">
{" "} {" "}
<CountUp <CountUp
end={currentMessage?.iodSources.scenario.total ?? 0} end={currentIodMessage?.scenario.total ?? 0}
duration={2.5} duration={2.5}
separator="," separator=","
/> />
@ -324,7 +330,13 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<span className="text-[#f00000]"> <span className="text-[#f00000]">
{" "} {" "}
<CountUp end={2.1} decimals={1} duration={duration} separator="," /> <CountUp
end={2.1}
decimals={1}
duration={duration}
separator=","
/>
</span> </span>
<span className="text-[#f00000]"> <span className="text-[#f00000]">
@ -340,7 +352,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<span className="text-green-700"> <span className="text-green-700">
{" "} {" "}
<CountUp <CountUp
end={currentMessage?.iodSources.organization.total ?? 0} end={currentIodMessage?.organization.total ?? 0}
duration={2.5} duration={2.5}
separator="," separator=","
/> />
@ -353,7 +365,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
) )
} }
] ]
}, [messages, iodLoading]) }, [showSearchData, iodLoading])
return ( return (
<Card <Card
@ -365,9 +377,9 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
<div <div
className={`absolute inset-0 pointer-events-none z-0 overflow-hidden ${showSearchData ? "" : ""}`}> 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"> <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={0} />
<CircleElement delay={1} playing={true} /> <CircleElement delay={1} />
<CircleElement delay={2} playing={true} /> <CircleElement delay={2} />
</div> </div>
</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"> <h2 className="text-xl font-semibold text-[#1a3c87] flex justify-center items-center">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<SearchIcon /> <SearchIcon />
{messages.length > 0 {currentIodMessage ? "科创数联网深度搜索" : "科创数联网连接资源"}
? "科创数联网深度搜索"
: "科创数联网连接资源"}
</div> </div>
{/*<button className="bg-[#2563eb1a] text-[#08307f] font-medium py-1 px-3 rounded-full text-sm hover:bg-[#2563eb1a] transition-colors float-right">*/} {/*<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}个结果*/} {/* {data.length}个结果*/}
{/*</button>*/} {/*</button>*/}
</h2> </h2>
<p className="text-sm text-[#1a3c87] mt-1 text-center"> <p className="text-sm text-[#1a3c87] mt-1 text-center">
{messages.length > 0 {currentIodMessage
? "下面是在科创数联网上进行深度搜索得到的相关数据、场景和团队" ? "下面是在科创数联网上进行深度搜索得到的相关数据、场景和团队"
: "下面是科创数联网连接的数据、场景和团队"} : "下面是科创数联网连接的数据、场景和团队"}
</p> </p>
@ -393,7 +403,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
{/* Content */} {/* Content */}
<div className="space-y-2 flex-1 overflow-y-auto"> <div className="space-y-2 flex-1 overflow-y-auto">
{messages.length ? ( {currentIodMessage ? (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key="search-results" key="search-results"
@ -422,16 +432,16 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
/> />
)} )}
</div> </div>
<p <div
className={`text-gray-700 ${showSearchData ? "text-sm" : "text-lg"}`}> className={`text-gray-700 ${showSearchData ? "text-sm" : "text-lg"}`}>
{item.title} {item.title}
</p> </div>
</div> </div>
{item.description && ( {item.description && (
<div className="flex-1"> <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} {item.description}
</p> </div>
</div> </div>
)} )}
</div> </div>

View File

@ -25,8 +25,10 @@ import { GenerationInfo } from "./GenerationInfo"
import { parseReasoning } from "@/libs/reasoning" import { parseReasoning } from "@/libs/reasoning"
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds" import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
import { AllIodRegistryEntry } from "@/types/iod.ts" import { AllIodRegistryEntry } from "@/types/iod.ts"
import { PiNetwork } from "react-icons/pi"
type Props = { type Props = {
id?: string
message: string message: string
message_type?: string message_type?: string
hideCopy?: boolean hideCopy?: boolean
@ -50,9 +52,11 @@ type Props = {
generationInfo?: any generationInfo?: any
isStreaming: boolean isStreaming: boolean
reasoningTimeTaken?: number 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 [isBtnPressed, setIsBtnPressed] = React.useState(false)
const [editMode, setEditMode] = React.useState(false) const [editMode, setEditMode] = React.useState(false)
@ -258,6 +262,18 @@ export const PlaygroundMessage = (props: Props) => {
)} )}
{props.isBot && ( {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 && ( {!props.hideCopy && (
<Tooltip title={t("copyToClipboard")}> <Tooltip title={t("copyToClipboard")}>
<button <button

View File

@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from "react" import React, { useMemo } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx" 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 { IodRegistryEntry } from "@/types/iod.ts"
import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx" import { useIodPlaygroundContext } from "@/components/Option/Playground/PlaygroundIod.tsx"
@ -33,7 +33,11 @@ type HeaderProps = {
showButton?: boolean showButton?: boolean
onClick?: () => void onClick?: () => void
} }
const Header: React.FC<HeaderProps> = ({ title, showButton = true, onClick }) => ( const Header: React.FC<HeaderProps> = ({
title,
showButton = true,
onClick
}) => (
<DataNavigation <DataNavigation
Header={ Header={
<div className="flex items-center text-[#4ab01a] gap-1"> <div className="flex items-center text-[#4ab01a] gap-1">
@ -97,36 +101,28 @@ const Main: React.FC<MainProps> = ({ data, loading, truncate = true }) => (
</div> </div>
) )
type Props = { type Props = {
className?: string className?: string
} }
export const PlaygroundScene: React.FC<Props> = ({ className }) => { export const PlaygroundScene: React.FC<Props> = ({ className }) => {
const { messages, iodLoading, currentMessageId, iodSearch } = const { iodLoading } = useMessageOption()
useMessageOption()
const { setShowPlayground, setDetailHeader, setDetailMain } = const {
useIodPlaygroundContext() setShowPlayground,
setDetailHeader,
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const data = useMemo<IodRegistryEntry[]>(() => {
// 确保loading状态时数据大于3 return currentIodMessage
if (iodLoading) { ? currentIodMessage.scenario?.data ?? []
return defaultData : defaultData
} }, [currentIodMessage])
if (messages.length && iodSearch) {
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
return currentMessage?.iodSources.scenario.data ?? []
}
return defaultData
}, [currentMessageId, messages, iodLoading])
const title = useMemo(() => { const title = useMemo(() => {
return messages.length > 0 ? "推荐场景" : "热点场景" return currentIodMessage ? "推荐场景" : "热点场景"
}, [messages]) }, [currentIodMessage])
const showMore = () => { const showMore = () => {
setShowPlayground(false) setShowPlayground(false)
@ -137,19 +133,17 @@ export const PlaygroundScene: React.FC<Props> = ({ className }) => {
onClick={() => setShowPlayground(false)} onClick={() => setShowPlayground(false)}
/> />
) )
setDetailMain(<Main loading={iodLoading} data={data} truncate={false} />) setDetailMain(<Main loading={iodLoading && Boolean(currentIodMessage)} data={data} truncate={false} />)
} }
return ( return (
<Card <Card className={`${className}`} hoverable>
className={`${className}`}
hoverable>
<div className="h-full flex flex-col gap-2 relative"> <div className="h-full flex flex-col gap-2 relative">
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <Header title={title} onClick={showMore} />
{/* 数据列表 */} {/* 数据列表 */}
<Main loading={iodLoading} data={data.slice(0, 3)} /> <Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
</div> </div>
</Card> </Card>
) )

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from "react" import React, { useMemo } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx" import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Skeleton } from "antd" import { Card, Skeleton } from "antd"
import { IodRegistryEntry } from "@/types/iod.ts" import { IodRegistryEntry } from "@/types/iod.ts"
@ -17,11 +17,10 @@ const defaultData: IodRegistryEntry[] = [
doId: "91320507MAEKWL5Y2L" doId: "91320507MAEKWL5Y2L"
}, },
{ {
name: "伊利诺伊大学香槟分校UIUC", name: "伊利诺伊大学香槟分校UIUC",
description: "创建于1867年坐落于伊利诺伊州双子城厄巴纳香槟市", description: "创建于1867年坐落于伊利诺伊州双子城厄巴纳香槟市",
doId: "bdware.org/uiuc", doId: "bdware.org/uiuc"
}, }
] ]
type HeaderProps = { type HeaderProps = {
@ -113,31 +112,24 @@ type Props = {
className?: string className?: string
} }
export const PlaygroundTeam: React.FC<Props> = ({ className }) => { export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
const { messages, iodLoading, currentMessageId, iodSearch } = const { iodLoading } = useMessageOption()
useMessageOption()
const { setShowPlayground, setDetailHeader, setDetailMain } = const {
useIodPlaygroundContext() setShowPlayground,
setDetailHeader,
setDetailMain,
currentIodMessage
} = useIodPlaygroundContext()
const data = useMemo<IodRegistryEntry[]>(() => { const data = useMemo<IodRegistryEntry[]>(() => {
// 确保loading状态时数据大于3 return currentIodMessage
if (iodLoading) { ? currentIodMessage.organization?.data ?? []
return defaultData : defaultData
} }, [currentIodMessage])
if (messages.length && iodSearch) {
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
return currentMessage?.iodSources.organization.data ?? []
}
return defaultData
}, [currentMessageId, messages, iodLoading])
const title = useMemo(() => { const title = useMemo(() => {
return messages.length > 0 ? "推荐团队" : "热点团队" return currentIodMessage ? "推荐团队" : "热点团队"
}, [messages]) }, [currentIodMessage])
const showMore = () => { const showMore = () => {
setShowPlayground(false) setShowPlayground(false)
@ -148,17 +140,18 @@ export const PlaygroundTeam: React.FC<Props> = ({ className }) => {
onClick={() => setShowPlayground(false)} 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 ( return (
<Card className={`${className}`} hoverable> <Card className={`${className}`} hoverable>
<div className="h-full flex flex-col gap-2 relative"> <div className="h-full flex flex-col gap-2 relative">
{/* 数据导航 */} {/* 数据导航 */}
<Header title={title} onClick={showMore} /> <Header title={title} onClick={showMore} />
{/* 数据列表 */} {/* 数据列表 */}
<Main loading={iodLoading} data={data.slice(0, 3)} /> <Main loading={iodLoading && Boolean(currentIodMessage)} data={data.slice(0, 3)} />
</div> </div>
</Card> </Card>
) )

View File

@ -1,15 +1,13 @@
import { Form, Image, Input, Modal, Tooltip, message } from "antd" import { Form, Image, Input, message, Modal } from "antd"
import { Share } from "lucide-react"
import { useState } from "react"
import type { Message } from "~/store/option"
import Markdown from "./Markdown"
import React from "react" import React from "react"
import Markdown from "./Markdown"
import { useMutation } from "@tanstack/react-query" import { useMutation } from "@tanstack/react-query"
import { getPageShareUrl } from "~/services/ollama" import { getPageShareUrl } from "~/services/ollama"
import { cleanUrl } from "~/libs/clean-url" import { cleanUrl } from "~/libs/clean-url"
import { getTitleById, getUserId, saveWebshare } from "@/db" import { getTitleById, getUserId, saveWebshare } from "@/db"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import fetcher from "@/libs/fetcher" import fetcher from "@/libs/fetcher"
import { Message } from "@/types/message.ts"
type Props = { type Props = {
messages: Message[] messages: Message[]

View File

@ -11,7 +11,8 @@ export const PlaygroundChat = () => {
regenerateLastMessage, regenerateLastMessage,
isSearchingInternet, isSearchingInternet,
editMessage, editMessage,
ttsEnabled ttsEnabled,
setCurrentMessageId,
} = useMessageOption() } = useMessageOption()
const [isSourceOpen, setIsSourceOpen] = React.useState(false) const [isSourceOpen, setIsSourceOpen] = React.useState(false)
const [source, setSource] = React.useState<any>(null) const [source, setSource] = React.useState<any>(null)
@ -27,6 +28,7 @@ export const PlaygroundChat = () => {
{messages.map((message, index) => ( {messages.map((message, index) => (
<PlaygroundMessage <PlaygroundMessage
key={index} key={index}
id={message.id}
isBot={message.isBot} isBot={message.isBot}
message={message.message} message={message.message}
name={message.name} name={message.name}
@ -49,6 +51,8 @@ export const PlaygroundChat = () => {
generationInfo={message?.generationInfo} generationInfo={message?.generationInfo}
isStreaming={streaming} isStreaming={streaming}
reasoningTimeTaken={message?.reasoning_time_taken} reasoningTimeTaken={message?.reasoning_time_taken}
setCurrentMessageId={setCurrentMessageId}
iodSearch={message.iodSearch}
/> />
))} ))}
</div> </div>

View File

@ -218,7 +218,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
{ {
key: 0, key: 0,
label: ( label: (
<p <div
onClick={() => { onClick={() => {
setIodSearch(true) setIodSearch(true)
}}> }}>
@ -227,13 +227,13 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<PiNetwork className="h-5 w-5" /> <PiNetwork className="h-5 w-5" />
</p> </p>
<p className="text-[#00000080]"></p> <p className="text-[#00000080]"></p>
</p> </div>
) )
}, },
{ {
key: 1, key: 1,
label: ( label: (
<p <div
onClick={() => { onClick={() => {
setIodSearch(false) setIodSearch(false)
}}> }}>
@ -242,7 +242,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<PiNetwork className="h-5 w-5" /> <PiNetwork className="h-5 w-5" />
</p> </p>
<p className="text-[#00000080]"></p> <p className="text-[#00000080]"></p>
</p> </div>
) )
} }
] ]
@ -397,7 +397,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
className={`!px-[5px] flex items-center justify-center dark:text-gray-300 ${ className={`!px-[5px] flex items-center justify-center dark:text-gray-300 ${
chatMode === "rag" ? "hidden" : "block" chatMode === "rag" ? "hidden" : "block"
}`}> }`}>
<ImageIcon stroke-width={1} className="h-5 w-5" /> <ImageIcon strokeWidth={1} className="h-5 w-5" />
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
@ -420,12 +420,12 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
}} }}
className={`flex items-center justify-center dark:text-gray-300 !px-[5px]`}> className={`flex items-center justify-center dark:text-gray-300 !px-[5px]`}>
{!isListening ? ( {!isListening ? (
<MicIcon stroke-width={1} className="h-5 w-5" /> <MicIcon strokeWidth={1} className="h-5 w-5" />
) : ( ) : (
<div className="relative"> <div className="relative">
<span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span> <span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
<MicIcon <MicIcon
stroke-width={1} strokeWidth={1}
className="h-5 w-5" className="h-5 w-5"
/> />
</div> </div>

View File

@ -9,7 +9,7 @@ import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
import { Card } from "antd" import { Card } from "antd"
import { CloseOutlined } from "@ant-design/icons" import { CloseOutlined } from "@ant-design/icons"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { Message } from "@/types/message.ts" import { AllIodRegistryEntry } from "@/types/iod.ts"
// 定义 Context 类型 // 定义 Context 类型
interface IodPlaygroundContextType { interface IodPlaygroundContextType {
@ -19,7 +19,7 @@ interface IodPlaygroundContextType {
setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>> setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>>
detailMain: React.ReactNode detailMain: React.ReactNode
setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>> setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>>
currentIodMessage: Message | null currentIodMessage?: AllIodRegistryEntry
} }
// 创建 Context // 创建 Context
@ -41,36 +41,34 @@ export const useIodPlaygroundContext = () => {
const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({ const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
children children
}) => { }) => {
const { messages, iodLoading, currentMessageId, iodSearch } = const { messages, iodLoading, currentMessageId } = useMessageOption()
useMessageOption()
const [showPlayground, setShowPlayground] = useState<boolean>(true) const [showPlayground, setShowPlayground] = useState<boolean>(true)
const [detailHeader, setDetailHeader] = useState(<></>) const [detailHeader, setDetailHeader] = useState(<></>)
const [detailMain, setDetailMain] = useState(<></>) const [detailMain, setDetailMain] = useState(<></>)
const currentIodMessage = useMemo<Message | null>(() => { const currentIodMessage = useMemo<AllIodRegistryEntry | undefined>(() => {
if (iodLoading) { console.log('messages', messages)
return null console.log("currentMessageId", currentMessageId)
console.log("iodLoading", iodLoading)
// loading 返回 undefined是为了避免数据不足三个的情况
if (iodLoading || !messages.length) {
return undefined
} }
if (messages.length && iodSearch) {
// 如果不存在currentMessageId默认返回最后一个message // 如果不存在currentMessageId默认返回最后一个message
if (!currentMessageId) { if (!currentMessageId) {
return messages.at(-1) const lastMessage = messages.at(-1)
// 如果最后一次message没有开启数联网搜索则返回undefined
return lastMessage?.iodSearch ? lastMessage.iodSources : undefined
} }
const currentMessage = messages?.find( const currentMessage = messages?.find(
(message) => message.id === currentMessageId (message) => message.id === currentMessageId
) )
if (currentMessage) { console.log("currentMessage", currentMessage)
return currentMessage return currentMessage?.iodSearch ? currentMessage.iodSources : undefined
} }, [currentMessageId, messages, iodLoading])
// 如果当前message不存在最后一个message
return messages.at(-1)
}
return null
}, [currentMessageId, messages, iodLoading, iodSearch])
return ( return (
<PlaygroundContext.Provider <PlaygroundContext.Provider
@ -154,7 +152,6 @@ const PlaygroundContent = () => {
) )
} }
export const PlaygroundIod = () => { export const PlaygroundIod = () => {
return ( return (
<div className="w-[36%] h-full pt-16 pr-5 pb-0"> <div className="w-[36%] h-full pt-16 pr-5 pb-0">

View File

@ -1,25 +0,0 @@
import { PencilIcon } from "lucide-react"
import { useMessage } from "../../../hooks/useMessage"
import { useTranslation } from 'react-i18next';
export const PlaygroundNewChat = () => {
const { setHistory, setMessages, setHistoryId } = useMessage()
const { t } = useTranslation('optionChat')
const handleClick = () => {
setHistoryId(null)
setMessages([])
setHistory([])
}
return (
<button
onClick={handleClick}
className="flex w-full border bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 focus:ring-offset-gray-100 rounded-md p-2 dark:border-gray-800">
<PencilIcon className="mx-3 h-5 w-5" aria-hidden="true" />
<span className="inline-flex font-semibol text-white text-sm">
{t('newChat')}
</span>
</button>
)
}

View File

@ -1,41 +1,20 @@
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import TextArea from "antd/es/input/TextArea" import TextArea from "antd/es/input/TextArea"
import { useEffect, useState } from "react" import { IodDb } from "@/db/iod.ts"
import { useState } from "react"
export const IodApp = () => { export const IodApp = () => {
const { t } = useTranslation("settings") const { t } = useTranslation("settings")
const db = IodDb.getInstance()
const [connectVal, setConnectVal] = useState<string>('') const [connection, setConnection] = useState(JSON.stringify(db.getIodConnection(), null, 2))
const setConnectValWrap = (val: string) => { const setConnectValWrap = (val: string) => {
localStorage.setItem("iod-connect", val) db.insertIodConnection(JSON.parse(val))
setConnectVal(val) setConnection(val)
} }
useEffect(() => {
const val = localStorage.getItem("iod-connect")
const defaultVal = {
gatewayUrl: "tcp://reg01.public.internetofdata.cn:21037",
registry: "data/Registry",
localRepository: "data/Repository",
doBrowser: "http://021.node.internetapi.cn:21030/SCIDE/SCManager"
}
if (!val) {
localStorage.setItem(
"iod-connect",
JSON.stringify(defaultVal)
)
setConnectVal(JSON.stringify(defaultVal, null, 2))
return
}
try {
const val = localStorage.getItem("iod-connect")
setConnectVal(JSON.stringify(JSON.parse(val), null, 2))
} catch (e) {
setConnectVal(JSON.stringify(defaultVal, null, 2))
}
}, [])
return ( return (
<dl className="flex flex-col space-y-6 text-sm"> <dl className="flex flex-col space-y-6 text-sm">
@ -47,7 +26,7 @@ export const IodApp = () => {
</div> </div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<span className="text-gray-700 dark:text-neutral-50"></span> <span className="text-gray-700 dark:text-neutral-50"></span>
<TextArea rows={6} placeholder="请输入数联网连接配置" value={connectVal} onChange={(e) => setConnectValWrap(e.target.value)} /> <TextArea rows={6} placeholder="请输入数联网连接配置" value={connection} onChange={(e) => setConnectValWrap(e.target.value)} />
</div> </div>
</dl> </dl>
) )

View File

@ -1,8 +1,7 @@
import { import { type ChatHistory as ChatHistoryType } from "~/store/option"
type ChatHistory as ChatHistoryType,
type Message as MessageType
} from "~/store/option"
import { AllIodRegistryEntry } from "@/types/iod.ts" import { AllIodRegistryEntry } from "@/types/iod.ts"
import { type Message as MessageType } from "@/types/message.ts"
import { getDefaultIodSources } from "@/libs/iod.ts"
type HistoryInfo = { type HistoryInfo = {
id: string id: string
@ -249,38 +248,31 @@ export const saveHistory = async (
await db.addChatHistory(history) await db.addChatHistory(history)
return history return history
} }
export type HistoryMessage = {
export const saveMessage = async ( history_id: string
history_id: string, name: string
name: string, role: string
role: string, content: string
content: string, images: string[]
images: string[], iodSearch?: boolean
webSources?: any[], webSearch?: boolean
iodSources?: AllIodRegistryEntry, webSources?: any[]
time?: number, iodSources?: AllIodRegistryEntry
message_type?: string, createdAt?: number
generationInfo?: any, messageType?: string
generationInfo?: any
reasoning_time_taken?: number reasoning_time_taken?: number
) => { }
export const saveMessage = async (msg: HistoryMessage): Promise<Message> => {
const id = generateID() const id = generateID()
let createdAt = Date.now() let createdAt = Date.now()
if (time) { if (msg.createdAt) {
createdAt += time createdAt += msg.createdAt
} }
const message = { const message = {
...msg,
id, id,
history_id,
name,
role,
content,
images,
createdAt, createdAt,
webSources,
iodSources,
messageType: message_type,
generationInfo: generationInfo,
reasoning_time_taken
} }
const db = new PageAssitDatabase() const db = new PageAssitDatabase()
await db.addMessage(message) await db.addMessage(message)
@ -304,11 +296,12 @@ export const formatToMessage = (messages: MessageHistory): MessageType[] => {
messages.sort((a, b) => a.createdAt - b.createdAt) messages.sort((a, b) => a.createdAt - b.createdAt)
return messages.map((message) => { return messages.map((message) => {
return { return {
...message,
isBot: message.role === "assistant", isBot: message.role === "assistant",
message: message.content, message: message.content,
name: message.name, name: message.name,
webSources: message?.webSources || [], webSources: message?.webSources || [],
iodSources: message?.iodSources || { data: [], scenario: [], organization: []}, iodSources: message?.iodSources || getDefaultIodSources(),
images: message.images || [], images: message.images || [],
generationInfo: message?.generationInfo, generationInfo: message?.generationInfo,
reasoning_time_taken: message?.reasoning_time_taken reasoning_time_taken: message?.reasoning_time_taken

74
src/db/iod.ts Normal file
View File

@ -0,0 +1,74 @@
const iodConnection = "iodConnection-g3"
export const defaultIodConnectionConfig = {
gatewayUrl: "tcp://reg01.public.internetofdata.cn:21037",
registry: "data/Registry",
localRepository: "data/Repository",
doBrowser: "http://021.node.internetapi.cn:21030/SCIDE/SCManager"
} as const
export type IodConnectionConfig = {
gatewayUrl: string
registry: string
localRepository: string
doBrowser: string
}
export class IodDb {
private static instance: IodDb
private static iodConnectionConfig: IodConnectionConfig | null = null
// 单例模式
static getInstance(): IodDb {
if (!IodDb.instance) {
IodDb.instance = new IodDb()
}
return IodDb.instance
}
insertIodConnection(config: IodConnectionConfig): void {
try {
localStorage.setItem(iodConnection, JSON.stringify(config))
IodDb.iodConnectionConfig = config
} catch (error) {
console.error('Failed to save IOD connection config:', error)
throw new Error('Failed to save IOD connection configuration')
}
}
getIodConnection(): IodConnectionConfig {
// 如果已经有缓存,直接返回
if (IodDb.iodConnectionConfig) {
return IodDb.iodConnectionConfig
}
try {
const val = localStorage.getItem(iodConnection)
if (!val) {
return defaultIodConnectionConfig
}
IodDb.iodConnectionConfig = JSON.parse(val)
} catch (error) {
console.warn('Failed to parse IOD connection config, using default:', error)
return defaultIodConnectionConfig
}
}
// 添加清除配置的方法
clearIodConnection(): void {
try {
localStorage.removeItem(iodConnection)
IodDb.iodConnectionConfig = null
} catch (error) {
console.error('Failed to clear IOD connection config:', error)
throw new Error('Failed to clear IOD connection configuration')
}
}
getIodConfig() {
return {
connection: this.getIodConnection(),
}
}
}

View File

@ -1,4 +1,4 @@
import { saveHistory, saveMessage } from "@/db" import { HistoryMessage, saveHistory, saveMessage } from "@/db"
import { import {
setLastUsedChatModel, setLastUsedChatModel,
setLastUsedChatSystemPrompt setLastUsedChatSystemPrompt
@ -23,7 +23,9 @@ export const saveMessageOnError = async ({
message_source = "web-ui", message_source = "web-ui",
message_type, message_type,
prompt_content, prompt_content,
prompt_id prompt_id,
iodSearch,
webSearch,
}: { }: {
e: any e: any
setHistory: (history: ChatHistory) => void setHistory: (history: ChatHistory) => void
@ -38,7 +40,9 @@ export const saveMessageOnError = async ({
message_source?: "copilot" | "web-ui" message_source?: "copilot" | "web-ui"
message_type?: string message_type?: string
prompt_id?: string prompt_id?: string
prompt_content?: string prompt_content?: string,
iodSearch?: boolean,
webSearch?: boolean,
}) => { }) => {
if ( if (
e?.name === "AbortError" || e?.name === "AbortError" ||
@ -59,31 +63,31 @@ export const saveMessageOnError = async ({
} }
]) ])
const defaultMessage: HistoryMessage = {
history_id: historyId,
name: selectedModel,
role: "assistant",
content: botMessage,
webSources: [],
iodSources: getDefaultIodSources(),
messageType: message_type,
iodSearch,
webSearch,
images: []
}
if (historyId) { if (historyId) {
if (!isRegenerating) { if (!isRegenerating) {
await saveMessage( await saveMessage({
historyId, ...JSON.parse(JSON.stringify(defaultMessage)),
selectedModel, role: "user",
"user", content: userMessage,
userMessage, images: [image]
[image], })
[],
getDefaultIodSources(),
1,
message_type
)
} }
await saveMessage( await saveMessage({
historyId, ...JSON.parse(JSON.stringify(defaultMessage))
selectedModel, })
"assistant",
botMessage,
[],
[],
getDefaultIodSources(),
2,
message_type
)
await setLastUsedChatModel(historyId, selectedModel) await setLastUsedChatModel(historyId, selectedModel)
if (prompt_id || prompt_content) { if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, { await setLastUsedChatSystemPrompt(historyId, {
@ -95,28 +99,19 @@ export const saveMessageOnError = async ({
const title = await generateTitle(selectedModel, userMessage, userMessage) const title = await generateTitle(selectedModel, userMessage, userMessage)
const newHistoryId = await saveHistory(title, false, message_source) const newHistoryId = await saveHistory(title, false, message_source)
if (!isRegenerating) { if (!isRegenerating) {
await saveMessage( await saveMessage({
newHistoryId.id, ...JSON.parse(JSON.stringify(defaultMessage)),
selectedModel, history_id: newHistoryId.id,
"user", content: userMessage,
userMessage, role: "user",
[image], images: [image]
[], })
getDefaultIodSources(),
1,
message_type
)
} }
await saveMessage( await saveMessage(
newHistoryId.id, {
selectedModel, ...JSON.parse(JSON.stringify(defaultMessage)),
"assistant", history_id: newHistoryId.id,
botMessage, },
[],
[],
getDefaultIodSources(),
2,
message_type
) )
setHistoryId(newHistoryId.id) setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel) await setLastUsedChatModel(newHistoryId.id, selectedModel)
@ -142,6 +137,8 @@ export const saveMessageOnSuccess = async ({
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
webSources, webSources,
iodSources, iodSources,
message_source = "web-ui", message_source = "web-ui",
@ -158,6 +155,8 @@ export const saveMessageOnSuccess = async ({
message: string message: string
image: string image: string
fullText: string fullText: string
iodSearch?: boolean
webSearch?: boolean
webSources: any[] webSources: any[]
iodSources: AllIodRegistryEntry iodSources: AllIodRegistryEntry
message_source?: "copilot" | "web-ui" message_source?: "copilot" | "web-ui"
@ -168,34 +167,38 @@ export const saveMessageOnSuccess = async ({
reasoning_time_taken?: number reasoning_time_taken?: number
}) => { }) => {
var botMessage var botMessage
const defaultMessage: HistoryMessage = {
history_id: historyId,
name: selectedModel,
role: "assistant",
content: fullText,
webSources: webSources,
iodSources: iodSources,
messageType: message_type,
images: [],
iodSearch,
webSearch,
generationInfo,
reasoning_time_taken,
}
if (historyId) { if (historyId) {
if (!isRegenerate) { if (!isRegenerate) {
await saveMessage( await saveMessage(
historyId, {
selectedModel, ...JSON.parse(JSON.stringify(defaultMessage)),
"user", role: "user",
message, content: message,
[image], images: [image],
[], webSources: [],
getDefaultIodSources(), iodSources: getDefaultIodSources(),
1, },
message_type,
generationInfo,
reasoning_time_taken
) )
} }
botMessage = await saveMessage( botMessage = await saveMessage(
historyId, {
selectedModel!, ...JSON.parse(JSON.stringify(defaultMessage)),
"assistant", }
fullText,
[],
webSources,
iodSources,
2,
message_type,
generationInfo,
reasoning_time_taken
) )
updateDialog(historyId, botMessage) updateDialog(historyId, botMessage)
await setLastUsedChatModel(historyId, selectedModel!) await setLastUsedChatModel(historyId, selectedModel!)
@ -209,30 +212,21 @@ export const saveMessageOnSuccess = async ({
const title = await generateTitle(selectedModel, message, message) const title = await generateTitle(selectedModel, message, message)
const newHistoryId = await saveHistory(title, false, message_source) const newHistoryId = await saveHistory(title, false, message_source)
await saveMessage( await saveMessage(
newHistoryId.id, {
selectedModel, ...JSON.parse(JSON.stringify(defaultMessage)),
"user", history_id: newHistoryId.id,
message, role: "user",
[image], content: message,
[], images: [image],
getDefaultIodSources(), webSources: [],
1, iodSources: getDefaultIodSources(),
message_type, },
generationInfo,
reasoning_time_taken
) )
botMessage = await saveMessage( botMessage = await saveMessage(
newHistoryId.id, {
selectedModel!, ...JSON.parse(JSON.stringify(defaultMessage)),
"assistant", history_id: newHistoryId.id,
fullText, }
[],
webSources,
iodSources,
2,
message_type,
generationInfo,
reasoning_time_taken
) )
updateDialog(newHistoryId.id, botMessage) updateDialog(newHistoryId.id, botMessage)
setHistoryId(newHistoryId.id) setHistoryId(newHistoryId.id)

View File

@ -2,12 +2,12 @@ import React from "react"
import { cleanUrl } from "~/libs/clean-url" import { cleanUrl } from "~/libs/clean-url"
import { import {
defaultEmbeddingModelForRag, defaultEmbeddingModelForRag,
geWebSearchFollowUpPrompt,
getOllamaURL, getOllamaURL,
geWebSearchFollowUpPrompt,
promptForRag, promptForRag,
systemPromptForNonRag systemPromptForNonRag
} from "~/services/ollama" } from "~/services/ollama"
import { useStoreMessageOption, type Message } from "~/store/option" import { useStoreMessageOption } from "~/store/option"
import { useStoreMessage } from "~/store" import { useStoreMessage } from "~/store"
import { SystemMessage } from "@langchain/core/messages" import { SystemMessage } from "@langchain/core/messages"
import { getDataFromCurrentTab } from "~/libs/get-html" import { getDataFromCurrentTab } from "~/libs/get-html"
@ -43,6 +43,7 @@ import {
} from "@/libs/reasoning" } from "@/libs/reasoning"
import { AllIodRegistryEntry } from "@/types/iod.ts" import { AllIodRegistryEntry } from "@/types/iod.ts"
import { getDefaultIodSources } from "@/libs/iod.ts" import { getDefaultIodSources } from "@/libs/iod.ts"
import { Message } from "@/types/message.ts"
export const useMessage = () => { export const useMessage = () => {
const { const {

View File

@ -7,7 +7,7 @@ import {
promptForRag, promptForRag,
systemPromptForNonRagOption systemPromptForNonRagOption
} from "~/services/ollama" } from "~/services/ollama"
import type { ChatHistory, Message, MeteringEntry } from "~/store/option" import type { ChatHistory, MeteringEntry } from "~/store/option"
import { useStoreMessageOption } from "~/store/option" import { useStoreMessageOption } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages" import { SystemMessage } from "@langchain/core/messages"
import { import {
@ -47,6 +47,7 @@ import {
removeReasoning removeReasoning
} from "@/libs/reasoning" } from "@/libs/reasoning"
import { getDefaultIodSources } from "@/libs/iod.ts" import { getDefaultIodSources } from "@/libs/iod.ts"
import type { Message } from "@/types/message.ts"
export const useMessageOption = () => { export const useMessageOption = () => {
const { const {
@ -214,36 +215,39 @@ export const useMessageOption = () => {
data: meter data: meter
}) })
let defaultMessage: Message = {
isBot: true,
name: selectedModel,
message,
iodSearch,
webSearch,
webSources: [],
iodSources: getDefaultIodSources(),
images: [image]
}
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
...messages, ...messages,
{ {
...JSON.parse(JSON.stringify(defaultMessage)),
id: generateID(),
isBot: false, isBot: false,
name: "You", name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
images: [image]
}, },
{ {
isBot: true, ...JSON.parse(JSON.stringify(defaultMessage)),
name: selectedModel, id: generateMessageId,
message: "", message: "",
webSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
} }
] ]
} else { } else {
newMessage = [ newMessage = [
...messages, ...messages,
{ {
isBot: true, ...JSON.parse(JSON.stringify(defaultMessage)),
name: selectedModel, id: generateMessageId,
message: "▋", message: " ",
webSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
} }
] ]
} }
@ -525,6 +529,8 @@ export const useMessageOption = () => {
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
webSources, webSources,
iodSources, iodSources,
generationInfo, generationInfo,
@ -566,7 +572,9 @@ export const useMessageOption = () => {
setHistory, setHistory,
setHistoryId, setHistoryId,
userMessage: message, userMessage: message,
isRegenerating: isRegenerate isRegenerating: isRegenerate,
iodSearch,
webSearch,
}) })
if (!errorSave) { if (!errorSave) {
@ -672,6 +680,7 @@ export const useMessageOption = () => {
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
setCurrentMessageId(generateMessageId)
const meter: MeteringEntry = { const meter: MeteringEntry = {
id: generateMessageId, id: generateMessageId,
queryContent: message, queryContent: message,
@ -682,7 +691,6 @@ export const useMessageOption = () => {
loading: true, loading: true,
data: meter data: meter
}) })
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
...messages, ...messages,
@ -690,6 +698,7 @@ export const useMessageOption = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
id: generateID(),
webSources: [], webSources: [],
iodSources: getDefaultIodSources(), iodSources: getDefaultIodSources(),
images: [image] images: [image]
@ -889,7 +898,6 @@ export const useMessageOption = () => {
content: fullText content: fullText
} }
]) ])
await saveMessageOnSuccess({ await saveMessageOnSuccess({
historyId, historyId,
setHistoryId, setHistoryId,
@ -898,6 +906,8 @@ export const useMessageOption = () => {
message, message,
image, image,
fullText, fullText,
iodSearch,
webSearch,
source: [], source: [],
generationInfo, generationInfo,
prompt_content: promptContent, prompt_content: promptContent,
@ -914,8 +924,8 @@ export const useMessageOption = () => {
const { cot, content } = responseResolver(fullText) const { cot, content } = responseResolver(fullText)
const currentMeteringEntry = { const currentMeteringEntry = {
...meter, ...meter,
modelInputTokenCount: prompt.length, modelInputTokenCount: prompt? prompt.length : 0,
modelOutputTokenCount: fullText.length, modelOutputTokenCount: fullText? fullText.length : 0,
model: ollama.modelName ?? ollama.model, model: ollama.modelName ?? ollama.model,
relatedDataCount: 0, relatedDataCount: 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(), timeTaken: new Date().getTime() - chatStartTime.getTime(),
@ -943,7 +953,9 @@ export const useMessageOption = () => {
userMessage: message, userMessage: message,
isRegenerating: isRegenerate, isRegenerating: isRegenerate,
prompt_content: promptContent, prompt_content: promptContent,
prompt_id: promptId prompt_id: promptId,
iodSearch,
webSearch,
}) })
if (!errorSave) { if (!errorSave) {
@ -1274,7 +1286,9 @@ export const useMessageOption = () => {
fullText, fullText,
source, source,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken,
iodSearch,
webSearch,
}) })
setIsProcessing(false) setIsProcessing(false)
@ -1290,7 +1304,9 @@ export const useMessageOption = () => {
setHistory, setHistory,
setHistoryId, setHistoryId,
userMessage: message, userMessage: message,
isRegenerating: isRegenerate isRegenerating: isRegenerate,
iodSearch,
webSearch,
}) })
if (!errorSave) { if (!errorSave) {

View File

@ -6,6 +6,7 @@ import {
} from "@/db" } from "@/db"
import { exportKnowledge, importKnowledge } from "@/db/knowledge" import { exportKnowledge, importKnowledge } from "@/db/knowledge"
import { exportVectors, importVectors } from "@/db/vector" import { exportVectors, importVectors } from "@/db/vector"
import { IodDb } from "@/db/iod"
import { message } from "antd" import { message } from "antd"
export const exportPageAssistData = async () => { export const exportPageAssistData = async () => {
@ -13,12 +14,14 @@ export const exportPageAssistData = async () => {
const chat = await exportChatHistory() const chat = await exportChatHistory()
const vector = await exportVectors() const vector = await exportVectors()
const prompts = await exportPrompts() const prompts = await exportPrompts()
const iod = IodDb.getInstance().getIodConfig()
const data = { const data = {
knowledge, knowledge,
chat, chat,
vector, vector,
prompts prompts,
iod
} }
const dataStr = JSON.stringify(data) const dataStr = JSON.stringify(data)
@ -34,6 +37,7 @@ export const exportPageAssistData = async () => {
} }
export const importPageAssistData = async (file: File) => { export const importPageAssistData = async (file: File) => {
debugger
const reader = new FileReader() const reader = new FileReader()
reader.onload = async () => { reader.onload = async () => {
try { try {
@ -55,6 +59,10 @@ export const importPageAssistData = async (file: File) => {
await importPrompts(data.prompts) await importPrompts(data.prompts)
} }
if(data?.iod) {
IodDb.getInstance().insertIodConnection(data.iod)
}
message.success("Data imported successfully") message.success("Data imported successfully")
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -1,28 +1,6 @@
import { Knowledge } from "@/db/knowledge" import { Knowledge } from "@/db/knowledge"
import { create } from "zustand" import { create } from "zustand"
import { AllIodRegistryEntry } from "@/types/iod.ts" import { Message } from "esbuild"
type WebSearch = {
search_engine: string
search_url: string
search_query: string
search_results: {
title: string
link: string
}[]
}
export type Message = {
isBot: boolean
name: string
message: string
webSources: any[]
iodSources: AllIodRegistryEntry
images?: string[]
search?: WebSearch
reasoning_time_taken?: number
id?: string
messageType?: string
}
export type ChatHistory = { export type ChatHistory = {
role: "user" | "assistant" | "system" role: "user" | "assistant" | "system"
@ -36,8 +14,11 @@ type State = {
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
history: ChatHistory history: ChatHistory
setHistory: (history: ChatHistory) => void setHistory: (history: ChatHistory) => void
currentMeteringEntry: {data: MeteringEntry, loading: boolean} currentMeteringEntry: { data: MeteringEntry; loading: boolean }
setCurrentMeteringEntry: (meteringEntry: {data: MeteringEntry, loading: boolean}) => void setCurrentMeteringEntry: (meteringEntry: {
data: MeteringEntry
loading: boolean
}) => void
meteringEntries: MeteringEntry[] meteringEntries: MeteringEntry[]
setMeteringEntries: (meteringEntries: MeteringEntry[]) => void setMeteringEntries: (meteringEntries: MeteringEntry[]) => void
streaming: boolean streaming: boolean
@ -123,9 +104,12 @@ export const useStoreMessageOption = create<State>((set) => ({
setMessages: (messages) => set({ messages }), setMessages: (messages) => set({ messages }),
history: [], history: [],
setHistory: (history) => set({ history }), setHistory: (history) => set({ history }),
currentMeteringEntry: {data: {} as MeteringEntry, loading: false}, currentMeteringEntry: { data: {} as MeteringEntry, loading: false },
setCurrentMeteringEntry: (currentMeteringEntry) => set({ currentMeteringEntry }), setCurrentMeteringEntry: (currentMeteringEntry) =>
meteringEntries: JSON.parse(localStorage.getItem("meteringEntries") || JSON.stringify([])), set({ currentMeteringEntry }),
meteringEntries: JSON.parse(
localStorage.getItem("meteringEntries") || JSON.stringify([])
),
setMeteringEntries: (meteringEntries) => set({ meteringEntries }), setMeteringEntries: (meteringEntries) => set({ meteringEntries }),
streaming: false, streaming: false,
setStreaming: (streaming) => set({ streaming }), setStreaming: (streaming) => set({ streaming }),

View File

@ -13,7 +13,9 @@ export type Message = {
isBot: boolean isBot: boolean
name: string name: string
message: string message: string
webSearch?: boolean
webSources: any[] webSources: any[]
iodSearch?: boolean
iodSources: AllIodRegistryEntry iodSources: AllIodRegistryEntry
images?: string[] images?: string[]
search?: WebSearch search?: WebSearch
@ -22,3 +24,5 @@ export type Message = {
generationInfo?: any generationInfo?: any
reasoning_time_taken?: number reasoning_time_taken?: number
} }
export type Messages = Message[]

View File

@ -13,6 +13,7 @@ import { PageAssitDatabase } from "@/db"
import { enPOSTag, Segment, useDefault } from "segmentit" import { enPOSTag, Segment, useDefault } from "segmentit"
import { getDefaultIodSources } from "@/libs/iod.ts" import { getDefaultIodSources } from "@/libs/iod.ts"
import { IodDb } from "@/db/iod.ts"
const segment = useDefault(new Segment()) const segment = useDefault(new Segment())
export const tokenizeInput = function (input: string): string[] { export const tokenizeInput = function (input: string): string[] {
@ -24,24 +25,9 @@ export const tokenizeInput = function (input: string): string[] {
) )
return words.filter((word) => word.w.length > 1).map((word) => word.w) return words.filter((word) => word.w.length > 1).map((word) => word.w)
} }
//doipUrl = tcp://reg01.public.internetofdata.cn:21037
export const _iodConfig = {
gatewayUrl: "tcp://reg01.public.internetofdata.cn:21037",
registry: "data/Registry",
localRepository: "data/Repository",
doBrowser: "http://021.node.internetapi.cn:21030/SCIDE/SCManager"
}
function getIodConfig() { function getIodConfig() {
const val = localStorage.getItem("iod-connect") return IodDb.getInstance().getIodConnection()
if (!val) {
return _iodConfig
}
try {
return JSON.parse(val)
} catch {
return _iodConfig
}
} }
export const iodConfigLocal = { export const iodConfigLocal = {
gatewayUrl: "tcp://127.0.0.1:21036", gatewayUrl: "tcp://127.0.0.1:21036",
@ -272,7 +258,7 @@ export const updateDialog = async function (
})) ?? [] })) ?? []
updateBody.IoDSources = updateBody.IoDSources =
Object.values( botMessage.iodSources as AllIodRegistryEntry).flatMap(iod => iod.data)?.map((r) => ({ Object.values((botMessage?.iodSources ?? {}) as AllIodRegistryEntry).flatMap(iod => iod.data)?.map((r) => ({
id: r.doId, id: r.doId,
tokenCount: tokenCount:
r.content || r.description r.content || r.description