Compare commits

..

No commits in common. "17020e8755e45af670de38135480991cd88b1e71" and "30aa0faaa17055e3f9e779419bb1a504d9abf194" have entirely different histories.

39 changed files with 1231 additions and 2085 deletions

View File

@ -59,7 +59,6 @@
"remark-gfm": "3.0.1",
"remark-math": "5.1.1",
"segmentit":"^2.0.3",
"styled-components": "^6.1.19",
"tesseract.js": "^5.1.1",
"turndown": "^7.1.3",
"unist-util-visit": "^5.0.0",
@ -77,7 +76,6 @@
"@types/react-dom": "18.2.18",
"@types/react-speech-recognition": "^3.9.5",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/styled-components": "^5.1.34",
"@types/turndown": "^5.0.4",
"autoprefixer": "^10.4.17",
"cross-env": "^7.0.3",

View File

@ -19,7 +19,7 @@
}
},
"tooltip": {
"searchInternet": "深度搜索",
"searchInternet": "搜索中...",
"searchIod": "搜索数联网",
"speechToText": "语音到文本",
"uploadImage": "上传图片",

View File

@ -316,10 +316,6 @@
"title": "管理知识",
"heading": "配置知识库"
},
"iodSettings": {
"title": "数联网 设置",
"heading": "配置数联网"
},
"rag": {
"title": "RAG 设置",
"ragSettings": {

View File

@ -16,19 +16,22 @@ export const DataNavigation: React.FC<Props> = ({ Header, showButton = true, onC
{/* 左侧部分 */}
<div className="flex items-center">
<Title
level={3}
className="flex items-center"
style={{ marginBottom: 0, color: "#1F2937", fontSize: "18px" }}>
level={4}
style={{ marginBottom: 0, color: "#1F2937", fontSize: "16px" }}>
{Header}
</Title>
</div>
{/* 右侧部分 */}
{showButton && (
<div className="flex items-center text-[#3a3a3a] cursor-pointer space-x-0.5 hover:text-[#00c0ef] transition" onClick={onClick}>
<span className="text-[12px]"></span>
<Button
color="default"
variant="link"
style={{ gap: 4 }}
onClick={onClick}>
<span className="text-sm"></span>
<ChevronRightIcon className="w-4 h-4" />
</div>
</Button>
)}
</div>
)

View File

@ -11,11 +11,6 @@ export const PageAssistProvider = ({
const [controller, setController] = React.useState<AbortController | null>(
null
)
const [iodLoading, setIodLoading] = React.useState<boolean>(false)
const [currentMessageId, setCurrentMessageId] = React.useState<string>('')
const [embeddingController, setEmbeddingController] =
React.useState<AbortController | null>(null)
@ -25,12 +20,6 @@ export const PageAssistProvider = ({
messages,
setMessages,
iodLoading,
setIodLoading,
currentMessageId,
setCurrentMessageId,
controller,
setController,

View File

@ -1,99 +1,57 @@
import React, { useMemo, useState } from "react"
import React from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, Skeleton } from "antd"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { IodRegistryEntry } from "@/types/iod.ts"
import { Card, Drawer, List } from "antd"
import { useCallback, useState } from "react"
const defaultData: IodRegistryEntry[] = [
export const PlaygroundData = () => {
// 模拟数据
const data: {
title: string
description: string
time: string
metadata?: string
}[] = [
{
name: "2019-2024年黄海清浅海域中河湖代数生物物种数据集",
doId: "CSTR:13452.11.01.11.2021.242",
title: "2019-2024年黄海清浅海域中河湖代数生物物种数据集",
description:
"数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心"
"数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第"
},
{
name: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023",
doId: "CSTR:13452.11.01.11.2021.343",
description: "黄海清浅海域中河湖代数生物物种数据集"
title: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023",
description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第"
},
{
name: "李嘉图为研究老虎沟大本营2014-2018年",
doId: "CSTR:3260.11.1528414789920489545",
title: "李嘉图为研究老虎沟大本营2014-2018年...",
description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新"
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第"
},
{
name: "青海玉树B1区俄日矿勘探数据2017-2023",
doId: "CSTR:3260.11.152841477420489545",
title: "青海玉树B1区俄日矿勘探数据2017-2023",
description:
"数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查"
"数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查",
time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第"
}
]
type ShowCardProps = {
loading: boolean
record: IodRegistryEntry
truncate?: boolean
for (let i = 0; i < 10; i++) {
data.push({
title: "中国资源环境网",
description: "中国资源环境网2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月"
})
}
const ShowCard: React.FC<ShowCardProps> = ({
loading,
record,
truncate = true
}) => (
<Card className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
{loading ? (
<Skeleton title={false} active />
) : (
<div className="flex flex-col gap-0.5">
<h3
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.name}>
{record.name}
</h3>
<p
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.doId}>
{record.doId}
</p>
<p
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
title={record.description}>
{record.description}
</p>
</div>
)}
</Card>
)
export const PlaygroundData = () => {
const { messages, iodLoading, currentMessageId, iodSearch } = useMessageOption()
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])
const title = useMemo(() => {
return messages.length > 0 ? "推荐数据" : "热点数据"
}, [messages])
const [open, setOpen] = useState(false)
const showDrawer = () => {
if (iodLoading) {
return
}
setOpen(true)
}
@ -102,28 +60,29 @@ export const PlaygroundData = () => {
}
return (
<Card
className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
<div className="flex flex-col h-full overflow-y-hidden">
{/* 数据导航 */}
<DataNavigation
Header={
<div className="flex items-center gap-0.5 text-[#3581e3]">
<div className="flex items-center text-[#1d4eD8] gap-1">
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3572"
width="18"
height="18">
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 32 32"
width="20"
height="20">
<defs></defs>
<g>
<circle cx="22" cy="24" r="2" fill="rgb(29, 78, 216)"></circle>
<path
d="M877.714286 54.857143H754.285714V9.142857c0-5.028571-4.114286-9.142857-9.142857-9.142857h-64c-5.028571 0-9.142857 4.114286-9.142857 9.142857v45.714286H498.285714V9.142857c0-5.028571-4.114286-9.142857-9.142857-9.142857h-64c-5.028571 0-9.142857 4.114286-9.142857 9.142857v45.714286H292.571429c-20.228571 0-36.571429 16.342857-36.571429 36.571428v137.142858h-109.714286c-20.228571 0-36.571429 16.342857-36.571428 36.571428v722.285714c0 20.228571 16.342857 36.571429 36.571428 36.571429h585.142857c20.228571 0 36.571429-16.342857 36.571429-36.571429v-109.714285h109.714286c20.228571 0 36.571429-16.342857 36.571428-36.571429V91.428571c0-20.228571-16.342857-36.571429-36.571428-36.571428zM685.714286 941.714286H192V310.857143h249.142857v198.857143c0 25.257143 20.457143 45.714286 45.714286 45.714285h198.857143v386.285715z m0-459.428572H514.285714V310.857143h0.228572L685.714286 482.057143v0.228571z m146.285714 313.142857h-64V448L548.571429 228.571429H338.285714v-91.428572h77.714286v36.571429c0 5.028571 4.114286 9.142857 9.142857 9.142857h64c5.028571 0 9.142857-4.114286 9.142857-9.142857v-36.571429h173.714286v36.571429c0 5.028571 4.114286 9.142857 9.142857 9.142857h64c5.028571 0 9.142857-4.114286 9.142857-9.142857v-36.571429h77.714286v658.285714z"
p-id="3573"
fill="#3581e3"></path>
fill="rgb(29, 78, 216)"
d="M29.777 23.479A8.64 8.64 0 0 0 22 18a8.64 8.64 0 0 0-7.777 5.479L14 24l.223.522A8.64 8.64 0 0 0 22 30a8.64 8.64 0 0 0 7.777-5.478L30 24zM22 28a4 4 0 1 1 4-4a4.005 4.005 0 0 1-4 4M7 17h5v2H7zm0-5h12v2H7zm0-5h12v2H7z"></path>
<path
fill="rgb(29, 78, 216)"
d="M22 2H4a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h8v-2H4V4h18v11h2V4a2.006 2.006 0 0 0-2-2"></path>
</g>
</svg>
{title}
</div>
}
onClick={showDrawer}
@ -131,26 +90,86 @@ export const PlaygroundData = () => {
{/* 数据列表 */}
<div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => {
return (
<ShowCard key={item.doId} loading={iodLoading} record={item} />
)
})}
{data.slice(0, 3).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-3 h-[148px]">
<div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2">
{item.title}
</h3>
<p className="text-xs text-gray-500 line-clamp-2">
{item.description}
</p>
<p className="text-gray-700 truncate">{item.time}</p>
{item.metadata && (
<div className="text-green-500 text-xs px-2 py-1 rounded-full flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 24 24"
width="12"
height="12">
<defs></defs>
<g>
<path
fill="rgb(34, 197, 94)"
d="m16 11.78l4.24-7.33l1.73 1l-5.23 9.05l-6.51-3.75L5.46 19H22v2H2V3h2v14.54L9.5 8z"></path>
</g>
</svg>
{item.metadata}
</div>
)}
</div>
</Card>
))}
</div>
{/* 抽屉 */}
<Drawer
title={title}
title="相关数据"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.map((item, index) => (
<ShowCard key={item.doId} loading={iodLoading} record={item} truncate={false} />
))}
width={600}>
<List
itemLayout="vertical"
dataSource={data}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={
<h3 className="text-sm font-medium text-gray-900">
{item.title}
</h3>
}
description={
<div className="space-y-1">
<p className="text-xs text-gray-500">{item.description}</p>
<p className="text-gray-700">{item.time}</p>
{item.metadata && (
<div className="text-green-500 text-xs px-2 py-1 rounded-full flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 24 24"
width="12"
height="12">
<defs></defs>
<g>
<path
fill="rgb(34, 197, 94)"
d="m16 11.78l4.24-7.33l1.73 1l-5.23 9.05l-6.51-3.75L5.46 19H22v2H2V3h2v14.54L9.5 8z"></path>
</g>
</svg>
{item.metadata}
</div>
)}
</div>
}
/>
</List.Item>
)}
/>
</Drawer>
</Card>
</div>
)
}

View File

@ -1,45 +1,13 @@
import { Sidebar } from "@/components/Option/Sidebar.tsx"
import React, { useContext, useMemo, useState } from "react"
import React, { useContext, useState } from "react"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStoreChatModelSettings } from "@/store/model.tsx"
import {
Button,
Card,
Divider,
List,
Menu,
MenuProps,
Popover,
Select,
Tooltip
} from "antd"
import { Card, Tooltip } from "antd"
import { PageAssitDatabase } from "@/db"
import { EraserIcon, PanelLeftIcon } from "lucide-react"
import { EraserIcon } from "lucide-react"
import { useTranslation } from "react-i18next"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useQueryClient } from "@tanstack/react-query"
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
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"
const ModelIcon = () => {
return (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9426"
width="16"
height="16">
<path
d="M509.952 161.512727c148.945455-82.850909 300.730182-91.229091 371.479273-20.945454s62.324364 221.509818-20.526546 370.501818h-0.465454a429.335273 429.335273 0 0 1 65.163636 284.392727 168.727273 168.727273 0 0 1-44.683636 85.643637 173.754182 173.754182 0 0 1-86.109091 44.683636 435.665455 435.665455 0 0 1-285.277091-65.675636 430.731636 430.731636 0 0 1-282.530909 63.813818 172.218182 172.218182 0 0 1-86.109091-44.683637c-70.283636-69.818182-62.370909-220.206545 19.502545-368.174545-81.966545-148.48-89.786182-298.309818-19.502545-368.686546s220.625455-62.324364 369.058909 19.130182z m291.886545 440.785455a901.818182 901.818182 0 0 1-92.16 106.589091 934.027636 934.027636 0 0 1-108.916363 93.602909 586.891636 586.891636 0 0 0 58.600727 21.410909c74.938182 22.341818 127.069091 19.502545 155.508364-8.843636l-0.465455 0.884363c28.811636-28.392727 31.697455-80.523636 8.843637-155.508363a546.443636 546.443636 0 0 0-21.41091-58.135273z m-582.74909-0.465455a539.927273 539.927273 0 0 0-20.433455 55.854546c-22.295273 75.357091-19.549091 127.022545 8.797091 155.368727s80.151273 31.697455 155.508364 8.936727h-0.558546a539.927273 539.927273 0 0 0 55.854546-20.526545 967.400727 967.400727 0 0 1-199.214546-199.726546z m290.90909-332.753454a851.781818 851.781818 0 0 0-131.258181 108.404363 823.296 823.296 0 0 0-109.847273 133.12 823.854545 823.854545 0 0 0 109.847273 133.12v-0.884363a852.293818 852.293818 0 0 0 131.211636 108.357818 846.754909 846.754909 0 0 0 133.538909-109.800727 856.436364 856.436364 0 0 0 108.962909-131.258182 852.852364 852.852364 0 0 0-108.962909-131.211637 829.998545 829.998545 0 0 0-133.538909-109.847272zM503.994182 418.909091a94.347636 94.347636 0 1 1-35.84 10.705454 92.811636 92.811636 0 0 1 35.84-10.705454z m310.877091-212.340364c-28.253091-28.299636-80.151273-31.557818-155.508364-8.750545a591.592727 591.592727 0 0 0-58.600727 21.876363 933.794909 933.794909 0 0 1 108.869818 93.556364 947.060364 947.060364 0 0 1 92.718545 107.054546 545.326545 545.326545 0 0 0 21.41091-58.181819q33.559273-113.058909-8.843637-155.508363zM363.054545 199.68c-74.938182-22.295273-127.069091-19.549091-155.508363 8.843636v-0.465454c-28.997818 28.392727-31.744 80.523636-8.936727 155.508363a507.345455 507.345455 0 0 0 20.433454 56.273455A976.663273 976.663273 0 0 1 418.909091 220.206545a541.230545 541.230545 0 0 0-55.854546-20.526545z m0 0"
fill="#696F85"
p-id="9427"></path>
</svg>
)
}
export const PlaygroundHistory = () => {
const { setSystemPrompt } = useStoreChatModelSettings()
@ -52,7 +20,6 @@ export const PlaygroundHistory = () => {
setHistoryId,
historyId,
clearChat,
selectedModel,
setSelectedModel,
temporaryChat,
setSelectedSystemPrompt
@ -62,163 +29,17 @@ export const PlaygroundHistory = () => {
const queryClient = useQueryClient()
type MenuItem = Required<MenuProps>["items"][number]
const qaPromptItems = useMemo<MenuItem[]>(() => {
return [
{
key: "qaPrompt",
label: "热点问题",
type: "group" as const,
children: qaPrompt.map((item) => {
return {
key: item.id,
label: <span title={item.title}>{item.title}</span>,
icon: <p className="w-3.5">{item.icon}</p>
}
})
}
]
}, [])
const { onSubmit } = useMessageOption()
const { mutateAsync: sendMessage } = useMutation({
mutationFn: onSubmit,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
}
})
const onClickQaPromptItem: MenuProps["onClick"] = (e) => {
const record = qaPrompt.find((item) => item.id === e.key)
void sendMessage({ message: record.title, image: "" })
}
// 大模型
const {
data: models,
isLoading: isModelsLoading,
refetch
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
return (
<Card
className={`flex flex-col [&_.ant-card-body]:h-full w-[300px] overflow-hidden h-full pb-5 transition-all duration-300 ease-in-out !bg-[#f3f4f6]`}
style={{ width: show ? "300px" : "0" }}>
{/*Header*/}
<div className="flex flex-col overflow-y-hidden h-full">
<div className="flex items-center justify-between transition-all duration-300 ease-in-out w-[250px]">
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
<span className="text-[#d30100]"></span>
</h2>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => {
setShow(!show)
}}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<div className="flex flex-col gap-1">
{/*新建对话*/}
<Button
color="purple"
variant="filled"
size="large"
className="w-full mt-4 hover:!bg-[#0057ff1a]"
style={{
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}}
onClick={clearChat}>
className={`flex flex-col [&>:nth-child(2)]:flex-1 [&>:nth-child(2)]:overflow-y-auto w-[300px] h-full pt-16 pb-5 transition-all duration-300 ease-in-out transform ${
show
? 'opacity-100 translate-x-0'
: 'opacity-0 -translate-x-full absolute'
}`}
style={{ paddingTop: "4rem" }}
title={
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
<PlusOutlined
className="text-sm"
style={{ fontSize: "16px", fontWeight: 500 }}
/>
<span className="font-medium ml-2.5">{t("newChat")}</span>
</div>
</div>
</Button>
{/*选择智能体*/}
<Popover
placement="right"
content={
<Select
className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
}>
<Button
size="large"
color="default"
variant="text"
className="w-full !justify-between !text-[#000000d9] font-normal">
<div className="flex items-center gap-2.5">
<ModelIcon />
<span className="!text-[#000000d9] font-normal text-sm">
</span>
</div>
<RightOutlined style={{ color: "#0000004d" }} />
</Button>
</Popover>
<Divider size="small" />
{/*热门搜索*/}
<Menu
items={qaPromptItems}
onClick={onClickQaPromptItem}
className="!bg-[#f3f4f6] !border-r-0"
/>
</div>
<Divider size="small" />
<div className="pb-1.5 pl-4 text-sm text-[#00000073] flex items-center justify-between pr-2">
<span></span>
{t("sidebarTitle")}
<Tooltip
title={t("settings:generalSettings.system.deleteChatHistory.label")}
placement="right">
@ -242,7 +63,8 @@ export const PlaygroundHistory = () => {
</button>
</Tooltip>
</div>
<div className="overflow-y-auto flex-1 pl-7">
}>
<div className="overflow-y-auto">
<Sidebar
onClose={() => setShow(true)}
setMessages={setMessages}
@ -257,7 +79,6 @@ export const PlaygroundHistory = () => {
history={history}
/>
</div>
</div>
</Card>
)
}

View File

@ -1,49 +1,7 @@
import React, { useMemo } from "react"
import { Card } from "antd"
// 使用 CSS-in-JS 方式
import styled, { keyframes } from "styled-components"
const rotate = keyframes`
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
`
const breathe = keyframes`
0% {
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
}
50% {
box-shadow: 0 0 20px rgba(37, 231, 232, 0.8);
}
100% {
box-shadow: 0 0 5px rgba(37, 231, 232, 0.3);
}
`
const CircleElement = styled.div<{ delay: number; playing: boolean }>`
position: absolute;
width: 300px;
height: 160px;
background: #3b82f6; // blue-500
opacity: 0.2;
border-radius: 50%;
top: 55%;
left: 50%;
animation:
${rotate} 6s linear infinite,
${breathe} 2s infinite alternate;
animation-delay: ${(props) => props.delay}s;
animation-play-state: ${(props) => (props.playing ? "running" : "paused")};
`
import React from 'react';
const SuccessIcon = () => {
return (
<svg
return (<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@ -55,188 +13,83 @@ const SuccessIcon = () => {
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
</svg>)
}
const LoadingIcon = () => {
return (
<svg
className="icon animate-spin"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="29588"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 24 24"
width="18"
height="18">
<defs></defs>
<g>
<path
d="M483.712 888.064a52.437333 52.437333 0 1 1 52.48 52.352 52.394667 52.394667 0 0 1-52.48-52.352z m-235.434667-53.76a65.578667 65.578667 0 1 1 46.421334 19.242667 65.962667 65.962667 0 0 1-46.378667-19.242667z m499.584-16.597333a41.984 41.984 0 0 1 59.264-59.434667 42.282667 42.282667 0 0 1 0 59.434667 41.941333 41.941333 0 0 1-59.264 0zM112.853333 546.602667a81.92 81.92 0 1 1 81.92 81.92 81.834667 81.834667 0 0 1-81.92-81.877334z m731.008 0a33.536 33.536 0 1 1 33.493334 33.578666 33.578667 33.578667 0 0 1-33.450667-33.536zM222.208 377.6a102.4 102.4 0 1 1 72.533333 29.866667 102.869333 102.869333 0 0 1-72.533333-29.824z m536.32-53.504a26.666667 26.666667 0 1 1 18.816 7.936 26.368 26.368 0 0 1-18.773333-7.893333zM414.378667 205.184a121.642667 121.642667 0 1 1 121.813333 121.6A121.728 121.728 0 0 1 414.378667 205.226667z"
p-id="29589"
fill="#4284f6"></path>
fill="rgb(59, 130, 246)"
d="M13 2.03v2.02c4.39.54 7.5 4.53 6.96 8.92c-.46 3.64-3.32 6.53-6.96 6.96v2c5.5-.55 9.5-5.43 8.95-10.93c-.45-4.75-4.22-8.5-8.95-8.97m-2 .03c-1.95.19-3.81.94-5.33 2.2L7.1 5.74c1.12-.9 2.47-1.48 3.9-1.68zM4.26 5.67A9.9 9.9 0 0 0 2.05 11h2c.19-1.42.75-2.77 1.64-3.9zM2.06 13c.2 1.96.97 3.81 2.21 5.33l1.42-1.43A8 8 0 0 1 4.06 13zm5.04 5.37l-1.43 1.37A10 10 0 0 0 11 22v-2a8 8 0 0 1-3.9-1.63M12.5 7v5.25l4.5 2.67l-.75 1.23L11 13V7z"></path>
</g>
</svg>
)
}
const SearchIcon = () => {
return (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2585"
width="22px"
height="22px">
<path
d="M913.365333 842.794667l-188.16-188.16a347.648 347.648 0 0 0 69.674667-209.194667c0-192.682667-156.757333-349.44-349.44-349.44s-349.44 156.757333-349.44 349.44 156.757333 349.44 349.44 349.44a347.648 347.648 0 0 0 209.152-69.674667l188.16 188.16a49.962667 49.962667 0 0 0 70.613333-70.570666zM195.84 445.44a249.6 249.6 0 1 1 249.6 249.6 249.898667 249.898667 0 0 1-249.6-249.6z"
fill="#08307f"
p-id="2586"></path>
</svg>
)
}
export const PlaygroundIodRelevant: React.FC = () => {
const { messages, iodLoading, currentMessageId, iodSearch } =
useMessageOption()
const showDescription = useMemo(() => {
return iodSearch && messages.length > 0 && !iodLoading
}, [iodSearch, messages, iodLoading])
const data = useMemo(() => {
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
return [
const data = [
{
title: (
<p className="font-extrabold">
<span className="text-[#f00000]"> 11 </span>
<span className="text-[#f00000]"> 500000+ </span>
</p>
),
description: showDescription ? (
<p>
<span className="text-green-700">
{" "}
{currentMessage?.iodSources.data.total}{" "}
</span>
</p>
) : (
""
)
title: "已在29个科学数据中心的50万个科学数据集中进行搜索",
description: "已发现4个数据集",
status: "success"
},
{
title: (
<p className="font-extrabold">
<span className="text-[#f00000]"> 1000000+ </span>
<span className="text-[#f00000]"> 50000+ </span>
</p>
),
description: showDescription ? (
<p>
<span className="text-green-700">
{" "}
{currentMessage?.iodSources.scenario.total}{" "}
</span>
</p>
) : (
""
)
title: "已在100万篇论文、2800个科创场景中进行搜索",
description: "已发现4个数据集",
status: "success"
},
{
title: (
<p className="font-extrabold">
<span className="text-[#f00000]"> 1000+ </span>
<span className="text-[#f00000]"> 763 </span>
<span className="text-[#f00000]"> 21000+ </span>
</p>
),
description: showDescription ? (
<p>
<span className="text-green-700">
{" "}
{currentMessage?.iodSources.organization.total}{" "}
</span>
</p>
) : (
""
)
}
title: "正在1000位智库专家、12万个创新机构中进行搜索",
status: "loading"
},
]
}, [messages, iodLoading])
for (let i = 0; i < 10; i++) {
data.push({
title: "已在29个科学数据中心的50万个科学数据集中进行搜索" + i,
description: "已发现4个数据集",
status: "success"
})
}
return (
<Card
hoverable
variant="outlined"
className="flex flex-col h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] translate-y-[-2px] !bg-[#f0f9ff]">
<div className="h-full flex flex-col relative">
{/* 花瓣效果 */}
<div className="absolute inset-0 pointer-events-none z-0 overflow-hidden">
<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} />
</div>
</div>
<div className="flex flex-col h-full">
{/* Header */}
<div className="p-3">
<h2 className="text-xl font-semibold text-[#1a3c87] flex justify-center items-center">
<div className="flex items-center gap-2">
<SearchIcon />
</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>*/}
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold text-gray-900">
</h2>
<p className="text-sm text-[#1a3c87] mt-1 text-center">
</p>
<span className="text-sm text-blue-600 font-medium">{data.length}</span>
</div>
{/* Content */}
<div className="space-y-2 flex-1 overflow-y-auto">
{data.map((item, index) => (
<Card
className="[&_.ant-card-body]:!p-3 [&_.ant-card-body]:h-full shadow-md h-[88px]"
key={index}>
<div
className={`flex flex-col gap-2 h-full items-start ${showDescription ? "justify-start" : "justify-center"}`}>
<div className="flex items-center gap-2">
<div className={`${showDescription ? "w-5 h-5" : "w-6 h-6"}`}>
{iodSearch && iodLoading ? (
<LoadingIcon />
) : (
<SuccessIcon />
)}
<div className="space-y-3 flex-1 overflow-y-auto">
{
data.map((item, index) => (
<div className="flex items-start space-x-3">
<div className="w-5 h-5 mt-1 flex-shrink-0">
{item.status === "success" ? <SuccessIcon /> : <LoadingIcon />}
</div>
<p
className={`text-gray-700 ${showDescription ? "text-sm" : "text-lg"}`}>
<div className="flex-1">
<p className="text-sm text-gray-700">
{item.title}
</p>
</div>
{item.description && (
<div className="flex-1">
<p className="text-xs text-gray-500 mt-1 pl-7">
{item.description}
</p>
</div>
)}
</div>
</Card>
))}
{item.description && <p className="text-xs text-gray-500 mt-1">{item.description}</p>}
</div>
</div>
))
}
</div>
</div>
</Card>
)
}

View File

@ -1,31 +1,30 @@
import Markdown from "../../Common/Markdown"
import React from "react"
import { Collapse, Image, Popover, Tag, Tooltip } from "antd"
import { Tag, Image, Tooltip, Collapse, Popover } from "antd"
import { WebSearch } from "./WebSearch"
import {
ArrowUpSquare,
CheckIcon,
ClipboardIcon,
InfoIcon,
MessageSquareShare,
Pen,
PlayIcon,
RotateCcw,
Square,
Star,
ThumbsUp,
ThumbsDown,
ThumbsUp
MessageSquareShare,
ArrowUpSquare
} from "lucide-react"
import { EditMessageForm } from "./EditMessageForm"
import { useTranslation } from "react-i18next"
import { MessageSource } from "./MessageSource"
import { useTTS } from "@/hooks/useTTS"
import { tagColors } from "@/utils/color"
import { removeModelSuffix } from "@/db/models"
import { GenerationInfo } from "./GenerationInfo"
import { parseReasoning } from "@/libs/reasoning"
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
import { AllIodRegistryEntry } from "@/types/iod.ts"
type Props = {
message: string
message_type?: string
@ -43,7 +42,7 @@ type Props = {
webSearch?: {}
isSearchingInternet?: boolean
webSources?: any[]
iodSources?: AllIodRegistryEntry
iodSources?: any[]
hideEditAndRegenerate?: boolean
onSourceClick?: (source: any) => void
isTTSEnabled?: boolean
@ -61,13 +60,12 @@ export const PlaygroundMessage = (props: Props) => {
return (
<div className="group relative flex w-full flex-col items-end justify-center pb-2 md:px-4 text-gray-800 dark:text-gray-100">
{/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */}
<div
className={`flex flex-row gap-1 md:gap-1 my-2 m-auto w-full ${props.isBot ? "" : "flex-row-reverse"}`}>
<div className="w-8 flex flex-col relative items-center justify-center bottom-[8px]">
<div className="flex flex-row gap-4 md:gap-6 my-2 m-auto w-full">
<div className="w-8 flex flex-col relative items-end">
<div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r">
{props.isBot ? (
!props.botAvatar ? (
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400 hidden"></div>
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div>
) : (
props.botAvatar
)
@ -79,7 +77,15 @@ export const PlaygroundMessage = (props: Props) => {
</div>
</div>
<div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]">
<span className="text-xs font-bold text-gray-800 dark:text-white"></span>
<span className="text-xs font-bold text-gray-800 dark:text-white">
{props.isBot
? props.name === "chrome::gemini-nano::page-assist"
? "Gemini Nano"
: removeModelSuffix(
props.name?.replaceAll(/accounts\/[^\/]+\/models\//g, "")
)
: "You"}
</span>
{props.isBot &&
props.isSearchingInternet &&
@ -93,7 +99,7 @@ export const PlaygroundMessage = (props: Props) => {
</Tag>
)}
</div>
<div className={`flex flex-grow flex-col`}>
<div className="flex flex-grow flex-col">
{!editMode ? (
props.isBot ? (
<>
@ -102,7 +108,6 @@ export const PlaygroundMessage = (props: Props) => {
return (
<Collapse
key={i}
defaultActiveKey={['reasoning']}
className="border-none !mb-3"
items={[
{
@ -134,10 +139,10 @@ export const PlaygroundMessage = (props: Props) => {
) : (
<p
className={`prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
props.message_type && "italic dark:text-gray-400"
} flex flex-row-reverse`}>
props.message_type &&
"italic text-gray-500 dark:text-gray-400 text-sm"
}`}>
{props.message}
{/*<span className="bg-[#0000000a] inline-block py-[9px] text-[#000000d9] rounded-xl px-4 font-light text-sm">{props.message}</span>*/}
</p>
)
) : (
@ -171,10 +176,9 @@ export const PlaygroundMessage = (props: Props) => {
<Collapse
className="mt-6"
ghost
// defaultActiveKey={['webSources']}
items={[
{
key: "webSources",
key: "1",
label: (
<div className="italic text-gray-500 dark:text-gray-400">
{t("webCitations")}
@ -196,14 +200,13 @@ export const PlaygroundMessage = (props: Props) => {
]}
/>
)}
{props.isBot && props?.iodSources && Object.values(props?.iodSources).map(item => item.data).flat().length > 0 && (
{props.isBot && props?.iodSources && props?.iodSources.length > 0 && (
<Collapse
className="mt-6"
ghost
// defaultActiveKey={['iod']}
items={[
{
key: "iod",
key: "1",
label: (
<div className="italic text-gray-500 dark:text-gray-400">
{t("iodCitations")}
@ -211,7 +214,7 @@ export const PlaygroundMessage = (props: Props) => {
),
children: (
<div className="mb-3 flex flex-wrap gap-2">
{Object.values(props?.iodSources).map(item => item.data).flat()?.map((source, index) => (
{props?.iodSources?.map((source, index) => (
<MessageSource
onSourceClick={props.onSourceClick}
key={index}
@ -318,7 +321,7 @@ export const PlaygroundMessage = (props: Props) => {
</button>
</Tooltip>
)}
{
{ (
<Tooltip title="收藏">
<button
aria-label="收藏"
@ -326,8 +329,8 @@ export const PlaygroundMessage = (props: Props) => {
<Star className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
}
{
)}
{ (
<Tooltip title="发布语用">
<button
aria-label="发布语用"
@ -335,8 +338,8 @@ export const PlaygroundMessage = (props: Props) => {
<ArrowUpSquare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
}
{
)}
{ (
<Tooltip title="发布对话">
<button
aria-label="发布对话"
@ -344,8 +347,8 @@ export const PlaygroundMessage = (props: Props) => {
<MessageSquareShare className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
}
{
)}
{ (
<Tooltip title="点赞">
<button
aria-label="点赞"
@ -353,8 +356,8 @@ export const PlaygroundMessage = (props: Props) => {
<ThumbsUp className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
}
{
)}
{ (
<Tooltip title="点踩">
<button
aria-label="点踩"
@ -362,7 +365,7 @@ export const PlaygroundMessage = (props: Props) => {
<ThumbsDown className="w-3 h-3 text-gray-400 group-hover:text-gray-500" />
</button>
</Tooltip>
}
)}
</div>
) : (
// add invisible div to prevent layout shift

View File

@ -1,98 +1,44 @@
import React, { useMemo, useState } from "react"
import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, Skeleton } from "antd"
import { IodRegistryEntry } from "@/types/iod.ts"
import { Card, Drawer, List } from "antd"
const defaultData: IodRegistryEntry[] = [
export const PlaygroundScene = () => {
// 模拟数据
const data = [
{
name: "绿色化工工艺项目",
title: "绿色化工工艺项目",
description:
"基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
doId: "CSTR:13552.11.01.61.2021.742"
demander: "奥赛康药业 供方美国Propella公司"
},
{
name: "智能农业解决方案",
title: "智能农业解决方案",
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
doId: "CSTR:14542.11.01.61.2031.528"
demander: "奥赛康药业 供方美国Propella公司"
},
{
name: "新能源汽车电池技术",
title: "新能源汽车电池技术",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
doId: "CSTR:147842.11.04.91.2031.680"
demander: "奥赛康药业 供方美国Propella公司"
},
{
name: "碳捕集与封存技术",
title: "碳捕集与封存技术",
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
doId: "CSTR:14242.19.11.61.2131.428"
demander: "奥赛康药业 供方美国Propella公司"
}
]
type ShowCardProps = {
loading: boolean
record: IodRegistryEntry
truncate?: boolean
for (let i = 0; i < 10; i++) {
data.push({
title: "开发新型电池材料",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
})
}
const ShowCard: React.FC<ShowCardProps> = ({
loading,
record,
truncate = true
}) => (
<Card className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
{loading ? (
<Skeleton title={false} active />
) : (
<div className="flex flex-col gap-0.5">
<h3
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.name}>
{record.name}
</h3>
<p
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.doId}>
{record.doId}
</p>
<p
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
title={record.description}>
{record.description}
</p>
</div>
)}
</Card>
)
export const PlaygroundScene = () => {
const { messages, iodLoading, currentMessageId, iodSearch } =
useMessageOption()
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])
const title = useMemo(() => {
return messages.length > 0 ? "推荐场景" : "热点场景"
}, [messages])
const [open, setOpen] = useState(false)
const showDrawer = () => {
if (iodLoading) {
return
}
setOpen(true)
}
@ -101,60 +47,96 @@ export const PlaygroundScene = () => {
}
return (
<Card
className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
<div className="h-full overflow-y-hidden flex flex-col">
{/* 数据导航 */}
<DataNavigation
Header={
<div className="flex items-center text-[#54c41d] gap-1">
<div className="flex items-center text-[#15803d] gap-1">
<svg
className="icon"
viewBox="0 0 1025 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6235"
width="18"
height="18">
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 32 32"
width="20"
height="20">
<defs></defs>
<g>
<path
d="M980.34571 1.143792c-4.850903 0-9.824354 0.888481-14.797806 2.930966L229.773215 299.724504H20.428686c-11.233669 0-20.424853 9.446494-20.424853 21.180572V702.584302c0 11.74429 9.191184 21.180572 20.424853 21.180573h129.820365c-4.728353 14.808018-7.271248 30.51473-7.271248 46.46654 0 84.119757 68.678568 152.543014 153.176184 152.543014 70.721053 0 130.330986-47.998404 147.93721-112.847312l521.569043 209.59984c4.983664 1.919936 9.957116 2.930966 14.808019 2.930967 21.568645 0 40.839493-18.127057 40.839493-42.371358V43.525362C1021.195415 19.270849 1002.047116 1.143792 980.34571 1.143792zM296.153987 831.250663c-33.833769 0-61.274559-27.308028-61.274558-61.009035 0-14.297397 4.983664-27.951411 14.042086-38.807221l108.374269 43.525362c-2.553107 31.403211-28.972654 56.290895-61.141797 56.290894z m633.12959 74.550713L263.984844 638.501326l-16.462431-6.638077H91.915671V391.626129h155.606742l16.462431-6.638077 665.298733-267.30005v788.113374z m0 0"
fill="#54c41d"
p-id="6236"></path>
fill="rgb(21, 128, 61)"
d="M16 18H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2M6 6v10h10V6zm20 6v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m0 12v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m-10 2v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2"></path>
</g>
</svg>
{title}
</div>
}
onClick={showDrawer}
/>
{/* 数据列表 */}
{/* 场景列表 */}
<div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => {
return (
<ShowCard key={item.doId} loading={iodLoading} record={item} />
)
})}
{data.slice(0,3).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-4 h-[148px]" >
<div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2">
{item.title}
</h3>
<p className="flex items-center gap-1.5">
<span className="inline-block bg-blue-100 text-green-800 text-xs px-2 py-1 rounded-full">
</span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
</span>
</p>
<p className="text-xs text-gray-500 truncate">{item.demander}</p>
<span className="text-gray-700 line-clamp-2">
{item.description}
</span>
</div>
</Card>
))}
</div>
{/* 抽屉 */}
<Drawer
title={title}
title="相关场景"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.map((item, index) => (
<ShowCard
key={item.doId}
loading={iodLoading}
record={item}
truncate={false}
/>
))}
width={600}>
<List
itemLayout="vertical"
dataSource={data}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={
<h3 className="text-sm font-medium text-gray-900">
{item.title}
</h3>
}
description={
<div className="space-y-1">
<p className="flex items-center gap-1.5">
<span className="inline-block bg-blue-100 text-green-800 text-xs px-2 py-1 rounded-full">
</span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
</span>
</p>
<p className="text-xs text-gray-500">
{item.demander}
</p>
<span className="text-gray-700">
{item.description}
</span>
</div>
}
/>
</List.Item>
)}
/>
</Drawer>
</Card>
</div>
)
}

View File

@ -1,86 +1,41 @@
import React, { useMemo, useState } from "react"
import React, { useState } from "react"
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
import { Card, Drawer, Skeleton } from "antd"
import { IodRegistryEntry } from "@/types/iod.ts"
type ShowCardProps = {
loading: boolean
record: IodRegistryEntry
truncate?: boolean
}
const ShowCard: React.FC<ShowCardProps> = ({
loading,
record,
truncate = true
}) => (
<Card className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
{loading ? (
<Skeleton title={false} active />
) : (
<div className="flex flex-col gap-0.5">
<h3
className={`text-base font-medium mb-1 text-[#222222] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.name}>
{record.name}
</h3>
<p
className={`text-sm text-[#383838] break-all ${truncate ? "line-clamp-2" : ""}`}
title={record.doId}>
{record.doId}
</p>
<p
className={`text-[#828282] text-xs break-all ${truncate ? "truncate" : ""}`}
title={record.description}>
{record.description}
</p>
</div>
)}
</Card>
)
const defaultData:IodRegistryEntry[] = [
{
name: "上海芯飞睿科技有限公司",
description:
"上海芯飞睿科技有限公司专业从事集成化激光材料与微型化激光器件的研发",
doId: "CSTR:15552.13.05.61.2022.783"
},
{
name: "长三角先进材料研究院",
description: "由江苏省人民政府联合中国科学院、中国钢研科技集团和中国",
doId: "CSTR:15552.12.01.11.2021.528"
},
{
name: "清华大学智能系统实验室",
description: "清华大学",
doId: "CSTR:15552.13.04.91.2021.614",
},
]
import { Card, Drawer, List } from "antd"
export const PlaygroundTeam = () => {
const { messages, iodLoading, currentMessageId, iodSearch } = useMessageOption()
const data = useMemo<IodRegistryEntry[]>(() => {
// 确保loading状态时数据大于3
if (iodLoading) {
return defaultData
// 模拟数据
const data = [
{
title: "绿色化工工艺项目",
description:
"基于生物基原料采用repeal2.0可降解材料技术,开发新型环保材料。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "智能农业解决方案",
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "新能源汽车电池技术",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
},
{
title: "碳捕集与封存技术",
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
demander: "奥赛康药业 供方美国Propella公司"
}
]
if (messages.length && iodSearch) {
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
return currentMessage?.iodSources.organization.data ?? []
for (let i = 0; i < 10; i++) {
data.push({
title: "开发新型电池材料",
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
demander: "奥赛康药业 供方美国Propella公司"
})
}
return defaultData
}, [currentMessageId, messages, iodLoading])
const title = useMemo(() => {
return messages.length > 0 ? "推荐团队" : "热点团队"
}, [messages])
const [open, setOpen] = useState(false)
const showDrawer = () => {
@ -92,63 +47,90 @@ export const PlaygroundTeam = () => {
}
return (
<Card
className="h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden"
hoverable>
<div className="h-full flex flex-col relative">
<div className="h-full overflow-y-hidden flex flex-col">
{/* 数据导航 */}
<DataNavigation
Header={
<div className="flex items-center text-[#BE0BAC] gap-1">
<div className="flex items-center text-[#15803d] gap-1">
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7272"
width="18"
height="18">
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
viewBox="0 0 32 32"
width="20"
height="20">
<defs></defs>
<g>
<path
d="M824.2 699.9c-25.4-25.4-54.7-45.7-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5-31.7 14.7-60.9 34.9-86.4 60.4C345 754.6 314 826.8 312 903.8c-0.1 4.5 3.5 8.2 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5C493.8 707.7 551.1 684 612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c0.1 4.3 3.7 7.7 8 7.7h56c4.5 0 8.1-3.7 8-8.2-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5-24.5-24.5-37.9-57.1-37.5-91.8 0.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-0.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5-24.2 24.2-56.4 37.5-90.6 37.5z"
p-id="7273"
fill="#BE0BAC"></path>
<path
d="M361.5 510.4c-0.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5 0.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1-25.8-25.2-39.7-59.3-38.7-95.4 0.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9 0.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-0.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204-0.1 4.5 3.5 8.2 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"
p-id="7274"
fill="#BE0BAC"></path>
fill="rgb(21, 128, 61)"
d="M16 18H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2M6 6v10h10V6zm20 6v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m0 12v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m-10 2v4h-4v-4zm0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2"></path>
</g>
</svg>
{title}
</div>
}
onClick={showDrawer}
/>
{/* 场景列表 */}
<div className="grid grid-cols-3 gap-3 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => (
<ShowCard key={item.doId} loading={iodLoading} record={item} />
))}
<div className="grid grid-cols-2 gap-3">
{data.slice(0,2).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-3" >
<div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2">
{item.title}
</h3>
<p className="flex items-center gap-1.5">
<span className="inline-block bg-blue-100 text-green-800 text-xs px-2 py-1 rounded-full">
</span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
</span>
</p>
<p className="text-xs text-gray-500 line-clamp-2">{item.description}</p>
</div>
</Card>
))}
</div>
{/* 抽屉 */}
<Drawer
title={title}
title="相关团队"
closable={{ "aria-label": "Close Button" }}
onClose={onClose}
open={open}
width="33.33%">
<div className="grid grid-cols-1 gap-3 overflow-y-auto">
{data.map((item, index) => (
<ShowCard
key={item.doId}
loading={iodLoading}
record={item}
truncate={false}
/>
))}
width={600}>
<List
itemLayout="vertical"
dataSource={data}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={
<h3 className="text-sm font-medium text-gray-900">
{item.title}
</h3>
}
description={
<div className="space-y-1">
<p className="flex items-center gap-1.5">
<span className="inline-block bg-blue-100 text-green-800 text-xs px-2 py-1 rounded-full">
</span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
</span>
</p>
<p className="text-xs text-gray-500">
{item.description}
</p>
</div>
}
/>
</List.Item>
)}
/>
</Drawer>
</Card>
</div>
)
}

View File

@ -1,33 +0,0 @@
import React from "react"
export const IodIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
className="icon"
viewBox="0 0 1088 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
fillRule="evenodd"
p-id="32289"
{...props}
ref={ref}
>
<path
d="M853.333333 458.666667h21.333334v21.333333h-21.333334zM680.533333 217.6h21.333334v21.333333h-21.333334zM740.266667 264.533333h21.333333v21.333334h-21.333333zM629.333333 177.066667h21.333334v21.333333h-21.333334zM398.933333 177.066667h21.333334v21.333333h-21.333334zM343.466667 217.6h21.333333v21.333333h-21.333333zM285.866667 264.533333h21.333333v21.333334h-21.333333zM174.933333 458.666667h21.333334v21.333333h-21.333334zM174.933333 520.533333h21.333334v21.333334h-21.333334zM174.933333 576h21.333334v21.333333h-21.333334zM292.266667 759.466667h21.333333v21.333333h-21.333333zM347.733333 800h21.333334v21.333333h-21.333334zM403.2 846.933333h21.333333v21.333334h-21.333333zM629.333333 846.933333h21.333334v21.333334h-21.333334zM454.4 514.133333h21.333333v21.333334h-21.333333zM512 514.133333h21.333333v21.333334h-21.333333zM567.466667 514.133333h21.333333v21.333334h-21.333333zM680.533333 800h21.333334v21.333333h-21.333334zM740.266667 759.466667h21.333333v21.333333h-21.333333zM853.333333 520.533333h21.333334v21.333334h-21.333334zM853.333333 576h21.333334v21.333333h-21.333334zM509.866667 189.866667h21.333333v147.2h-21.333333zM509.866667 708.266667h21.333333v147.2h-21.333333zM225.92 672.170667l127.488-73.6 10.666667 18.474666-127.488 73.6zM223.509333 375.125333l10.666667-18.474666 127.466667 73.6-10.666667 18.474666zM677.312 422.186667l132.309333-64.554667 9.344 19.2-132.309333 64.512zM684.544 618.517333l10.986667-18.282666 126.186666 75.797333-10.986666 18.304z"
p-id="32290"></path>
<path
d="M520.533333 590.933333c-93.866667 0-189.866667-23.466667-189.866666-66.133333s96-66.133333 189.866666-66.133333 189.866667 23.466667 189.866667 66.133333-93.866667 66.133333-189.866667 66.133333z m0-110.933333c-102.4 0-168.533333 27.733333-168.533333 44.8s66.133333 44.8 168.533333 44.8c102.4 0 168.533333-27.733333 168.533334-44.8s-64-44.8-168.533334-44.8z"
p-id="32291"></path>
<path
d="M520.533333 714.666667c-36.266667 0-57.6-68.266667-64-130.133334l21.333334-2.133333c8.533333 76.8 29.866667 110.933333 42.666666 110.933333 12.8 0 34.133333-34.133333 42.666667-113.066666l21.333333 2.133333c-6.4 64-25.6 132.266667-64 132.266667z m44.8-243.2c-8.533333-78.933333-29.866667-115.2-42.666666-115.2-12.8 0-34.133333 36.266667-42.666667 113.066666l-21.333333-2.133333c6.4-64 27.733333-132.266667 64-132.266667 38.4 0 57.6 70.4 64 134.4l-21.333334 2.133334zM520.533333 209.066667c-36.266667 0-66.133333-29.866667-66.133333-66.133334 0-36.266667 29.866667-66.133333 66.133333-66.133333 36.266667 0 66.133333 29.866667 66.133334 66.133333 2.133333 36.266667-27.733333 66.133333-66.133334 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8 25.6 0 44.8-21.333333 44.8-44.8 2.133333-23.466667-19.2-44.8-44.8-44.8zM857.6 407.466667c-36.266667 0-66.133333-29.866667-66.133333-66.133334 0-36.266667 29.866667-66.133333 66.133333-66.133333s66.133333 29.866667 66.133333 66.133333c2.133333 36.266667-27.733333 66.133333-66.133333 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8s44.8-21.333333 44.8-44.8c2.133333-23.466667-19.2-44.8-44.8-44.8zM857.6 776.533333c-36.266667 0-66.133333-29.866667-66.133333-66.133333 0-36.266667 29.866667-66.133333 66.133333-66.133333s66.133333 29.866667 66.133333 66.133333c2.133333 34.133333-27.733333 66.133333-66.133333 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8 44.8-21.333333 44.8-44.8-19.2-44.8-44.8-44.8zM520.533333 974.933333c-36.266667 0-66.133333-29.866667-66.133333-66.133333 0-36.266667 29.866667-66.133333 66.133333-66.133333 36.266667 0 66.133333 29.866667 66.133334 66.133333 2.133333 36.266667-27.733333 66.133333-66.133334 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8c25.6 0 44.8-21.333333 44.8-44.8s-19.2-44.8-44.8-44.8zM183.466667 407.466667c-36.266667 0-66.133333-29.866667-66.133334-66.133334 0-36.266667 29.866667-66.133333 66.133334-66.133333 36.266667 0 66.133333 29.866667 66.133333 66.133333 2.133333 36.266667-27.733333 66.133333-66.133333 66.133334z m0-113.066667c-25.6 0-44.8 21.333333-44.8 44.8 0 25.6 21.333333 44.8 44.8 44.8 25.6 0 44.8-21.333333 44.8-44.8 2.133333-23.466667-19.2-44.8-44.8-44.8zM183.466667 776.533333c-36.266667 0-66.133333-29.866667-66.133334-66.133333 0-36.266667 29.866667-66.133333 66.133334-66.133333 36.266667 0 66.133333 29.866667 66.133333 66.133333 2.133333 34.133333-27.733333 66.133333-66.133333 66.133333z m0-113.066666c-25.6 0-44.8 21.333333-44.8 44.8s21.333333 44.8 44.8 44.8c25.6 0 44.8-21.333333 44.8-44.8s-19.2-44.8-44.8-44.8z"
p-id="32292"></path>
<path
d="M514.133333 731.733333c-117.333333 0-215.466667-96-215.466666-215.466666 0-117.333333 96-215.466667 215.466666-215.466667 117.333333 0 215.466667 96 215.466667 215.466667s-96 215.466667-215.466667 215.466666z m0-386.133333c-93.866667 0-172.8 76.8-172.8 172.8s76.8 172.8 172.8 172.8c93.866667 0 172.8-76.8 172.8-172.8s-76.8-172.8-172.8-172.8z"
p-id="32293"></path>
</svg>
)
})

View File

@ -1,136 +1,268 @@
import React, { useContext, useMemo } from "react"
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
import { PanelLeftIcon } from "lucide-react"
import { Button, Tooltip } from "antd"
import { PlusOutlined } from "@ant-design/icons"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStorage } from "@plasmohq/storage/hook"
import {
BrainCog,
ChevronLeft,
ChevronRight,
CogIcon,
ComputerIcon,
GaugeCircle,
GithubIcon,
PanelLeftIcon,
ZapIcon
} from "lucide-react"
import { useTranslation } from "react-i18next"
import { NavLink, useLocation } from "react-router-dom"
interface SettingIconProps {}
const SettingIcon: React.FC<SettingIconProps> = () => {
return (
<svg
// @ts-ignore
t="1755767517454"
className="icon"
viewBox="0 0 1084 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="10420"
width="20"
height="20">
<path
d="M1072.147851 406.226367c-6.331285-33.456782-26.762037-55.073399-52.047135-55.073399-0.323417 0-0.651455 0.003081-0.830105 0.009241l-4.655674 0c-73.124722 0-132.618162-59.491899-132.618162-132.618162 0-23.731152 11.447443-50.336101 11.546009-50.565574 13.104573-29.498767 3.023185-65.672257-23.427755-84.127081l-1.601687-1.127342-134.400039-74.661726-1.700252-0.745401c-8.753836-3.805547-18.334698-5.735272-28.479231-5.735272-20.789593 0-41.235746 8.344174-54.683758 22.306575-14.741683 15.216028-65.622973 58.649474-104.721083 58.649474-39.450789 0-90.633935-44.286652-105.438762-59.784516-13.518857-14.247316-34.128258-22.753199-55.127302-22.753199-9.945862 0-19.354234 1.861961-27.958682 5.531982l-1.746455 0.74078-139.141957 76.431283-1.643269 1.139662c-26.537186 18.437884-36.675557 54.579032-23.584845 84.062398 0.115506 0.264895 11.579891 26.725075 11.579891 50.634877 0 73.126262-59.491899 132.618162-132.618162 132.618162l-4.581749 0c-0.318797-0.00616-0.636055-0.01078-0.951772-0.01078-25.260456 0-45.672728 21.618157-52.002472 55.0811-0.462025 2.453354-11.313456 60.622322-11.313456 106.117939 0 45.494078 10.85143 103.659965 11.314996 106.119479 6.334365 33.458322 26.758957 55.076479 52.036353 55.076479 0.320337 0 0.651455-0.00616 0.842426-0.012321l4.655674 0c73.126262 0 132.618162 59.491899 132.618162 132.616622 0 23.760413-11.444363 50.333021-11.546009 50.565574-13.093793 29.474125-3.041666 65.646075 23.395414 84.151722l1.569346 1.093459 131.838879 73.726895 1.675611 0.7377c8.750757 3.84251 18.305437 5.790715 28.397607 5.790715 21.082208 0 41.676209-8.706094 55.0888-23.290689 18.724339-20.347588 69.527086-62.362616 107.04815-62.362616 40.625872 0 92.72537 47.100385 107.759669 63.583903 13.441852 14.831008 34.176001 23.689571 55.470741 23.695731l0.00616 0c9.895039 0 19.27877-1.883523 27.893999-5.598205l1.711034-0.73924 136.659342-75.531873 1.617088-1.128882c26.492523-18.456365 36.601633-54.600594 23.538642-84.016195-0.115506-0.267974-11.595291-27.082374-11.595291-50.67646 0-73.124722 59.49344-132.616622 132.618162-132.616622l4.517066-0.00154c0.300316 0.00616 0.599092 0.009241 0.899409 0.009241 25.331299-0.00154 45.785153-21.619697 52.107197-55.054918 0.112426-0.589852 11.325776-59.507301 11.325776-106.14104C1083.464388 466.640776 1072.609877 408.67356 1072.147851 406.226367zM377.486862 945.656142l-115.32764-64.487932c5.082277-13.052211 15.437801-43.51815 15.437801-75.017486 0-109.382917-84.176364-199.816642-192.587488-208.134635-2.647404-15.427021-8.873963-54.967133-8.873963-85.667166 0-30.65691 6.223479-70.232445 8.869343-85.671786 108.415744-8.311832 192.592108-98.745557 192.592108-208.134635 0-31.416171-10.300081-61.797405-15.371577-74.854236l122.721583-67.40331c0.003081 0 0.00462 0.00154 0.007701 0.00154 4.423121 4.518606 22.121764 22.080182 46.558275 39.493911 39.929754 28.46229 77.952885 42.894416 113.014434 42.894416 34.716571 0 72.437845-14.151831 112.115025-42.06431 24.282503-17.07953 41.896442-34.302288 46.308782-38.74543 0.009241-0.00154 0.018481-0.00462 0.026182-0.00616l118.301542 65.726159c-5.077657 13.055291-15.416239 43.499669-15.416239 74.958962 0 109.389077 84.174824 199.822802 192.590568 208.134635 2.645865 15.462442 8.872423 55.107281 8.872423 85.671786 0 30.687711-6.223479 70.241685-8.869343 85.673326C890.042174 606.334084 805.86427 696.767809 805.86427 806.158426c0 31.450053 10.317022 61.851309 15.393138 74.903519l-119.783103 66.198965c-5.168521-5.490399-22.603811-23.363073-46.740005-41.288109-40.701336-30.224145-79.662378-45.549521-115.800446-45.549521-35.79155 0-74.458435 15.038919-114.927219 44.694774C400.22004 922.554885 382.666163 940.255068 377.486862 945.656142zM731.271848 511.646647c0-105.803762-86.081448-191.88059-191.888289-191.88059-105.803762 0-191.88059 86.076827-191.88059 191.88059 0 105.803762 86.076827 191.882129 191.88059 191.882129C645.19194 703.528777 731.271848 617.450409 731.271848 511.646647zM539.383558 395.903184c63.825696 0 115.751164 51.922387 115.751164 115.743463 0 63.825696-51.925468 115.751164-115.751164 115.751164-63.821076 0-115.743463-51.925468-115.743463-115.751164C423.640095 447.824031 475.562482 395.903184 539.383558 395.903184z"
fill="#272636"
p-id="10421"></path>
</svg>
)
}
import { useLocation, NavLink } from "react-router-dom"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import { Select, Tooltip } from "antd"
import { getAllPrompts } from "@/db"
import { ProviderIcons } from "../Common/ProviderIcon"
import { NewChat } from "./NewChat"
import { PageAssistSelect } from "../Select"
import { MoreOptions } from "./MoreOptions"
import { useContext } from "react"
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
type Props = {
sidebarOpen: boolean
setSidebarOpen: () => void
setOpenModelSettings: (open: boolean) => void
}
export const Header: React.FC<Props> = ({ setOpenModelSettings }) => {
const location = useLocation()
export const Header: React.FC<Props> = ({
setOpenModelSettings,
setSidebarOpen,
sidebarOpen
}) => {
const { t, i18n } = useTranslation(["option", "common"])
const isRTL = i18n?.dir() === "rtl"
const { show, setShow } = useContext(HistoryContext)
const showLeft = useMemo<boolean>(() => {
console.log(location.pathname)
if (location.pathname.includes("/settings")) {
return true
const [shareModeEnabled] = useStorage("shareMode", false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming,
historyId,
temporaryChat
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
refetch
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
return show
}, [location.pathname, show])
const { t } = useTranslation(["option", "common", "settings"])
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
const { clearChat } = useMessageOption()
return (
<div
className={`h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${show && !location.pathname.includes("/settings") ? "left-[300px]" : ""}`}>
{/*控制侧边栏显示隐藏与新建对话*/}
{!showLeft && (
<div className="flex items-center gap-3">
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => {
setShow(!show)
}}>
<PanelLeftIcon className="w-6 h-6" />
</button>
<Button
color="cyan"
variant="filled"
shape="round"
style={{
color: "#0057ff",
background: "#0057ff0f",
border: "1px solid #0066ff26"
}}
onClick={clearChat}>
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
<PlusOutlined
className="text-sm"
style={{ fontSize: "16px", fontWeight: 500 }}
/>
<span>{t("newChat")}</span>
</div>
</div>
</Button>
</div>
)}
{location.pathname.includes("/settings") && (
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
temporaryChat && "!bg-gray-200 dark:!bg-black"
}`}>
<div className="flex gap-2 items-center">
{pathname !== "/" && (
<div>
<NavLink
to="/"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<span className="text-[#d30100]"></span>
</NavLink>
</h2>
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
{isRTL ? (
<ChevronRight className={`w-8 h-8`} />
) : (
<ChevronLeft className={`w-8 h-8`} />
)}
{/* 项目标题 */}
<div
className={`
absolute left-1/2 transform -translate-x-1/2
w-[600px] h-[60px] dark:bg-black
flex items-center justify-center
transition-[top] drop-shadow
${show ? "-top-[60px]" : "-top-[2px] delay-200"}
`}>
<svg
// @ts-ignore
t="1755653236428"
className="icon"
viewBox="0 0 8960 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9634"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%"
height="55">
<path
d="M8960 0c-451.52 181.184-171.2 1024-992 1024H992C171.232 1024 451.392 181.184 0 0h8960z"
fill="#ffffff"
p-id="9635"></path>
</svg>
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3 absolute left-1/2 transform -translate-x-1/2">
<span className="text-[#d30100]"></span>
</h2>
</NavLink>
</div>
{/*设置框*/}
<div className="flex items-center ml-auto pr-5">
)}
<div style={{width: sidebarOpen ? "288px" : "205px"}} className="flex items-center justify-between transition-all duration-300 ease-in-out">
<h2
className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"
style={{ lineHeight: "0" }}>
{t("projectTitle")}
</h2>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen()}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<NewChat clearChat={clearChat} />
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect
selectedSystemPrompt={selectedSystemPrompt}
setSelectedSystemPrompt={setSelectedSystemPrompt}
setSelectedQuickPrompt={setSelectedQuickPrompt}
/>
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{messages.length > 0 && !streaming && (
<MoreOptions
shareModeEnabled={shareModeEnabled}
historyId={historyId}
messages={messages}
/>
)}
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<SettingIcon />
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
<Tooltip title={t("metering")}>
<NavLink
to="/metering"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GaugeCircle className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from "react"
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
import { Header } from "./Header.tsx"
import { Header } from "./Header"
interface History {
show: boolean
@ -33,11 +33,15 @@ export default function OptionLayout({
return (
<div className="flex h-full w-full">
<main className="relative h-dvh w-full">
{/*<div className="relative z-10 w-full">*/}
{/*</div>*/}
<div className="relative z-10 w-full">
<Header
sidebarOpen={showHistory}
setSidebarOpen={useToggle}
setOpenModelSettings={setOpenModelSettings}
/>
</div>
{/* <div className="relative flex h-full flex-col items-center"> */}
<HistoryContext.Provider value={historyContextValue}>
<Header setOpenModelSettings={setOpenModelSettings} />
{children}
</HistoryContext.Provider>
{/* </div> */}

View File

@ -1,18 +1,17 @@
import {
BlocksIcon,
BookIcon,
BrainCircuitIcon,
ChromeIcon,
CombineIcon,
CpuIcon,
InfoIcon,
OrbitIcon,
ShareIcon
ShareIcon,
BlocksIcon,
InfoIcon,
CombineIcon,
ChromeIcon,
CpuIcon
} from "lucide-react"
import { useTranslation } from "react-i18next"
import { Link, useLocation } from "react-router-dom"
import { OllamaIcon } from "../Icons/Ollama"
import { IodIcon } from "../Icons/Iod.tsx"
import { BetaTag } from "../Common/Beta"
function classNames(...classes: string[]) {
@ -83,12 +82,6 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
icon={OllamaIcon}
current={location.pathname}
/>
<LinkComponent
href="/settings/iod"
name={t("iodSettings.title")}
icon={IodIcon}
current={location.pathname}
/>
{import.meta.env.BROWSER === "chrome" && (
<LinkComponent
href="/settings/chrome"

View File

@ -1,264 +0,0 @@
import { useStorage } from "@plasmohq/storage/hook"
import {
BrainCog,
ChevronLeft,
ChevronRight,
CogIcon,
ComputerIcon,
GaugeCircle,
GithubIcon,
PanelLeftIcon,
ZapIcon
} from "lucide-react"
import { useTranslation } from "react-i18next"
import { useLocation, NavLink } from "react-router-dom"
import { SelectedKnowledge } from "../Option/Knowledge/SelectedKnowledge"
import { ModelSelect } from "../Common/ModelSelect"
import { PromptSelect } from "../Common/PromptSelect"
import { useQuery } from "@tanstack/react-query"
import { fetchChatModels } from "~/services/ollama"
import { useMessageOption } from "~/hooks/useMessageOption"
import { Select, Tooltip } from "antd"
import { getAllPrompts } from "@/db"
import { ProviderIcons } from "../Common/ProviderIcon"
import { NewChat } from "./NewChat"
import { MoreOptions } from "./MoreOptions"
type Props = {
sidebarOpen: boolean
setSidebarOpen: () => void
setOpenModelSettings: (open: boolean) => void
}
export const Header: React.FC<Props> = ({
setOpenModelSettings,
setSidebarOpen,
sidebarOpen
}) => {
const { t, i18n } = useTranslation(["option", "common"])
const isRTL = i18n?.dir() === "rtl"
const [shareModeEnabled] = useStorage("shareMode", false)
const [hideCurrentChatModelSettings] = useStorage(
"hideCurrentChatModelSettings",
false
)
const {
selectedModel,
setSelectedModel,
clearChat,
selectedSystemPrompt,
setSelectedQuickPrompt,
setSelectedSystemPrompt,
messages,
streaming,
historyId,
temporaryChat
} = useMessageOption()
const {
data: models,
isLoading: isModelsLoading,
} = useQuery({
queryKey: ["fetchModel"],
queryFn: () => fetchChatModels({ returnEmpty: true }),
refetchIntervalInBackground: false,
placeholderData: (prev) => prev
})
const { data: prompts, isLoading: isPromptLoading } = useQuery({
queryKey: ["fetchAllPromptsLayout"],
queryFn: getAllPrompts
})
const { pathname } = useLocation()
const getPromptInfoById = (id: string) => {
return prompts?.find((prompt) => prompt.id === id)
}
const handlePromptChange = (value?: string) => {
if (!value) {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(undefined)
return
}
const prompt = getPromptInfoById(value)
if (prompt?.is_system) {
setSelectedSystemPrompt(prompt.id)
} else {
setSelectedSystemPrompt(undefined)
setSelectedQuickPrompt(prompt!.content)
}
}
return (
<div
className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b dark:bg-[#171717] dark:border-gray-600 ${
temporaryChat && "!bg-gray-200 dark:!bg-black"
}`}>
<div className="flex gap-2 items-center">
{pathname !== "/" && (
<div>
<NavLink
to="/"
className="text-gray-500 items-center dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
{isRTL ? (
<ChevronRight className={`w-8 h-8`} />
) : (
<ChevronLeft className={`w-8 h-8`} />
)}
</NavLink>
</div>
)}
<div style={{width: sidebarOpen ? "288px" : "205px"}} className="flex items-center justify-between transition-all duration-300 ease-in-out">
<h2
className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3"
style={{ lineHeight: "0" }}>
<span className="text-[#d30100]"></span>
</h2>
<button
className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen()}>
<PanelLeftIcon className="w-6 h-6" />
</button>
</div>
<NewChat clearChat={clearChat} />
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
className="w-80"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}}
filterOption={(input, option) => {
//@ts-ignore
return (
option?.label?.props["data-title"]
?.toLowerCase()
?.indexOf(input.toLowerCase()) >= 0
)
}}
showSearch
loading={isModelsLoading}
options={models?.map((model) => ({
label: (
<span
key={model.model}
data-title={model.name}
className="flex flex-row gap-3 items-center ">
<ProviderIcons
provider={model?.provider}
className="w-5 h-5"
/>
<span className="line-clamp-2">{model.name}</span>
</span>
),
value: model.model
}))}
size="large"
// onRefresh={() => {
// refetch()
// }}
/>
</div>
<div className="lg:hidden">
<ModelSelect />
</div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600">
{"/"}
</span>
<div className="hidden lg:block">
<Select
size="large"
loading={isPromptLoading}
showSearch
placeholder={t("selectAPrompt")}
className="w-60"
allowClear
onChange={handlePromptChange}
value={selectedSystemPrompt}
filterOption={(input, option) =>
//@ts-ignore
option.label.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
options={prompts?.map((prompt) => ({
label: (
<span
key={prompt.title}
className="flex flex-row gap-3 items-center">
{prompt.is_system ? (
<ComputerIcon className="w-4 h-4" />
) : (
<ZapIcon className="w-4 h-4" />
)}
{prompt.title}
</span>
),
value: prompt.id
}))}
/>
</div>
<div className="lg:hidden">
<PromptSelect
selectedSystemPrompt={selectedSystemPrompt}
setSelectedSystemPrompt={setSelectedSystemPrompt}
setSelectedQuickPrompt={setSelectedQuickPrompt}
/>
</div>
<SelectedKnowledge />
</div>
<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<div className="flex gap-4 items-center">
{messages.length > 0 && !streaming && (
<MoreOptions
shareModeEnabled={shareModeEnabled}
historyId={historyId}
messages={messages}
/>
)}
{!hideCurrentChatModelSettings && (
<Tooltip title={t("common:currentChatModelSettings")}>
<button
onClick={() => setOpenModelSettings(true)}
className="!text-gray-500 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<BrainCog className="w-6 h-6" />
</button>
</Tooltip>
)}
<Tooltip title={t("githubRepository")}>
<a
href="https://github.com/n4ze3m/page-assist"
target="_blank"
className="!text-gray-500 hidden lg:block dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GithubIcon className="w-6 h-6" />
</a>
</Tooltip>
<Tooltip title={t("settings")}>
<NavLink
to="/settings"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<CogIcon className="w-6 h-6" />
</NavLink>
</Tooltip>
<Tooltip title={t("metering")}>
<NavLink
to="/metering"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<GaugeCircle className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,4 +1,4 @@
import React, { useContext } from "react"
import React from "react"
import { Card } from "antd"
@ -6,7 +6,7 @@ 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 { PlaygroundData } from '@/components/Common/Playground/Data.tsx'
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
import {
@ -20,9 +20,11 @@ import { useStoreChatModelSettings } from "@/store/model"
import { useSmartScroll } from "@/hooks/useSmartScroll"
import { ChevronDown } from "lucide-react"
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
import { PlaygroundTokenStatistics } from "@/components/Common/Playground/TokenStatistics.tsx"
import { PlaygroundHistory } from "@/components/Common/Playground/History.tsx"
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
export const Playground = () => {
const drop = React.useRef<HTMLDivElement>(null)
const [dropedFile, setDropedFile] = React.useState<File | undefined>()
@ -145,14 +147,13 @@ export const Playground = () => {
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
} bg-white dark:bg-[#171717]`}>
<PlaygroundHistory />
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
<div className="relative h-full flex-1 prose-lg flex justify-center [&>*]:max-w-[848px]">
<div
ref={containerRef}
className="custom-scrollbar flex h-auto w-full flex-col items-center px-5">
className="custom-scrollbar bg-bottom-mask-light dark:bg-bottom-mask-dark mask-bottom-fade will-change-mask flex h-full w-full flex-col items-center overflow-x-hidden overflow-y-auto px-5">
<PlaygroundChat />
</div>
<div
className={`${messages.length ? "absolute" : "relative"} bottom-0 w-full`}>
<div className="absolute bottom-0 w-full">
{!isAtBottom && (
<div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center">
<button
@ -165,21 +166,20 @@ export const Playground = () => {
<PlaygroundForm dropedFile={dropedFile} />
</div>
</div>
{/*auto_530px_165px*/}
<div
className="w-4/12 h-full grid grid-rows-12 gap-3 pt-16 pr-5 pb-0"
style={{ paddingTop: "4rem" }}>
<div className="w-full row-span-5">
{messages.length && (
<div className="w-1/4 h-full grid grid-rows-[auto_530px_165px] pt-16 pr-5 pb-0 border-l border-gray-200" style={{"paddingTop": "4rem"}}>
<div className="w-full overflow-y-auto border-gray-200 border-b p-3">
<PlaygroundIodRelevant />
</div>
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
<div className="w-full grid grid-cols-2 gap-3 custom-scrollbar border-gray-200 border-b p-3">
<PlaygroundData />
<PlaygroundScene />
</div>
<div className="w-full row-span-3 pb-3">
<div className="w-full p-3 pb-0">
<PlaygroundTeam />
</div>
</div>
)}
</div>
)
}

View File

@ -18,7 +18,7 @@ export const PlaygroundChat = () => {
return (
<>
<div className="relative flex w-full flex-col items-center pb-4">
<div className="relative flex w-full flex-col items-center pt-16 pb-4">
{messages.length === 0 && (
<div className="mt-3 w-full">
<PlaygroundEmpty />
@ -52,7 +52,8 @@ export const PlaygroundChat = () => {
/>
))}
</div>
{messages.length !== 0 && <div className="w-full pb-[157px]"></div>}
<div className="w-full pb-[157px]"></div>
<MessageSourcePopup
open={isSourceOpen}
setOpen={setIsSourceOpen}

View File

@ -1,14 +1,73 @@
import { Card, Col, Row } from "antd"
import RocketSvg from '@/assets/icons/rocket.svg'
import BulbSvg from '@/assets/icons/bulb.svg'
import EyeSvg from '@/assets/icons/eye.svg'
import ASvg from '@/assets/icons/a.svg'
import BSvg from '@/assets/icons/b.svg'
import CSvg from '@/assets/icons/c.svg'
import DSvg from '@/assets/icons/d.svg'
import ESvg from '@/assets/icons/e.svg'
import FSvg from '@/assets/icons/f.svg'
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { qaPrompt } from "@/libs/playground.tsx"
export const PlaygroundEmpty = () => {
const { onSubmit } = useMessageOption()
const {
onSubmit,
setMessages,
setHistory,
setHistoryId,
historyId,
clearChat,
setSelectedModel,
temporaryChat,
setSelectedSystemPrompt
} = useMessageOption()
const queryClient = useQueryClient()
const questions = [
{
title: "如何开发一个适合超大型城市的碳普惠方法学?",
icon: <img src={RocketSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何开发一个零碳园区的数字化评价系统?",
icon: <img src={BulbSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何开发一个碳定价预测系统?",
icon: <img src={EyeSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "新药临床研究如何提升实验安全性?",
icon: <img src={ASvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何加速新药申报和审批?",
icon: <img src={BSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何研制与司美格鲁肽相似的新药?",
icon: <img src={CSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何解决固态电池的成本和寿命难题?",
icon: <img src={DSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何解决船舶制造中的材料腐蚀难题?",
icon: <img src={ESvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何解决船舶制造中流体模拟和建模优化难题?",
icon: <img src={FSvg} alt="Rocket" className="w-10 my-0" />,
},
]
const { mutateAsync: sendMessage } = useMutation({
mutationFn: onSubmit,
onSuccess: () => {
@ -18,36 +77,32 @@ export const PlaygroundEmpty = () => {
}
})
function handleQuestion(message: string) {
void sendMessage({ message, image: "", isRegenerate: true })
void sendMessage({message, image: ''})
}
return (
<div className="w-full pb-4 pt-[20%]">
<div className="w-full p-4">
{/* 标题区域 */}
<div className="mb-4">
<h2
className="text-xl font-bold text-gray-800"
style={{ lineHeight: "0" }}>
</h2>
<h2 className="text-xl font-bold text-gray-800" style={{lineHeight: '0'}}></h2>
<p className="text-sm text-gray-500"></p>
</div>
{/* 卡片网格布局 */}
<Row gutter={[16, 16]} className="w-full">
{qaPrompt.map((item, index) => (
{questions.map((item, index) => (
<Col key={index} xs={24} sm={12} md={8}>
<Card
hoverable
style={{backgroundColor: "#f3f4f6"}}
className="border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 cursor-pointer"
onClick={() => handleQuestion(item.title)}>
onClick={() => handleQuestion(item.title)}
>
<div className="flex items-center">
<div className="text-blue-500 mr-2 w-10">{item.icon}</div>
<div className="font-medium text-sm text-gray-800">
{item.title}
</div>
<div className="text-blue-500 mr-2">{item.icon}</div>
<div className="font-medium text-sm text-gray-800">{item.title}</div>
</div>
</Card>
</Col>

View File

@ -4,7 +4,8 @@ import React 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 { Checkbox, Dropdown, Switch, Tooltip } from "antd"
import { Image } from "antd"
import { useWebUI } from "~/store/webui"
import { defaultEmbeddingModelForRag } from "~/services/ollama"
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
@ -233,7 +234,8 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/>
</div>
<div>
<div className={`flex bg-transparent `}>
<div
className={`flex bg-transparent `}>
<form
onSubmit={form.onSubmit(async (value) => {
stopListening()
@ -302,10 +304,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<div className="flex">
{!selectedKnowledge && (
<div>
{/* 展示隐藏深度搜索*/}
<Tooltip
title={t("tooltip.searchInternet")}
className="hidden">
<Tooltip title={t("tooltip.searchInternet")}>
<div className="inline-flex items-center gap-2">
<PiGlobe
className={`h-5 w-5 dark:text-gray-300 `}
@ -318,9 +317,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/>
</div>
</Tooltip>
<Tooltip
title={t("tooltip.searchIod")}
className="ml-3">
<Tooltip title={t("tooltip.searchIod")} className="ml-3">
<div className="inline-flex items-center gap-2">
<PiNetwork
className={`h-5 w-5 dark:text-gray-300 `}
@ -360,10 +357,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
if (isListening) {
stopSpeechRecognition()
} else {
console.log(
"开始语音识别,语言:",
speechToTextLanguage
)
resetTranscript()
startListening({
continuous: true,

View File

@ -1,54 +0,0 @@
import { useTranslation } from "react-i18next"
import TextArea from "antd/es/input/TextArea"
import { useEffect, useState } from "react"
export const IodApp = () => {
const { t } = useTranslation("settings")
const [connectVal, setConnectVal] = useState<string>('')
const setConnectValWrap = (val: string) => {
localStorage.setItem("iod-connect", val)
setConnectVal(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 (
<dl className="flex flex-col space-y-6 text-sm">
<div>
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
{t("iodSettings.heading")}
</h2>
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
</div>
<div className="flex flex-col gap-3">
<span className="text-gray-700 dark:text-neutral-50"></span>
<TextArea rows={6} placeholder="请输入数联网连接配置" value={connectVal} onChange={(e) => setConnectValWrap(e.target.value)} />
</div>
</dl>
)
}

View File

@ -24,7 +24,6 @@ import {
getLastUsedChatSystemPrompt,
lastUsedChatModelEnabled
} from "@/services/model-settings"
import { useState } from "react"
type Props = {
onClose: () => void
@ -170,12 +169,7 @@ export const Sidebar = ({
{group.items.map((chat, index) => (
<div
key={index}
className={`
flex py-2 px-2 items-center gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out border
hover:text-[#000000d9] hover:bg-[#f3f2ff] dark:hover:bg-[#2d2d2d] dark:border-gray-800
hover:[&_.more-vertical]:text-[#000000d9]
${historyId === chat.id ? 'text-[#000000d9] bg-[#f3f2ff] border-[#000000d9]' : 'dark:text-gray-100 text-gray-800'}
`}>
className="flex py-2 px-2 items-center gap-3 relative rounded-md truncate hover:pr-4 group transition-opacity duration-300 ease-in-out bg-gray-100 dark:bg-[#232222] dark:text-gray-100 text-gray-800 border hover:bg-gray-200 dark:hover:bg-[#2d2d2d] dark:border-gray-800">
{chat?.message_source === "copilot" && (
<Tooltip title={t("common:sidebarChat")} placement="top">
<BotIcon className="size-3 text-green-500" />
@ -271,7 +265,7 @@ export const Sidebar = ({
trigger={["click"]}
placement="bottomRight">
<button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100">
<MoreVertical className={`group-hover:text-[#000000d9] w-4 h-4 more-vertical ${historyId === chat.id ? 'text-[#000000d9]' : ''}`} />
<MoreVertical className="w-4 h-4" />
</button>
</Dropdown>
</div>

View File

@ -5,12 +5,6 @@ interface PageAssistContext {
messages: Message[]
setMessages: Dispatch<SetStateAction<Message[]>>
currentMessageId: string
setCurrentMessageId: Dispatch<SetStateAction<string>>
iodLoading: boolean
setIodLoading: Dispatch<SetStateAction<boolean>>
controller: AbortController | null
setController: Dispatch<SetStateAction<AbortController>>
@ -22,12 +16,6 @@ export const PageAssistContext = createContext<PageAssistContext>({
messages: [],
setMessages: () => {},
currentMessageId: "",
setCurrentMessageId: () => {},
iodLoading: false,
setIodLoading: () => {},
controller: null,
setController: () => {},

View File

@ -2,7 +2,6 @@ import {
type ChatHistory as ChatHistoryType,
type Message as MessageType
} from "~/store/option"
import { AllIodRegistryEntry } from "@/types/iod.ts"
type HistoryInfo = {
id: string
@ -31,7 +30,7 @@ type Message = {
content: string
images?: string[]
webSources?: string[]
iodSources?: AllIodRegistryEntry
iodSources?: string[]
search?: WebSearch
createdAt: number
reasoning_time_taken?: number
@ -257,7 +256,7 @@ export const saveMessage = async (
content: string,
images: string[],
webSources?: any[],
iodSources?: AllIodRegistryEntry,
iodSources?: any[],
time?: number,
message_type?: string,
generationInfo?: any,
@ -308,7 +307,7 @@ export const formatToMessage = (messages: MessageHistory): MessageType[] => {
message: message.content,
name: message.name,
webSources: message?.webSources || [],
iodSources: message?.iodSources || { data: [], scenario: [], organization: []},
iodSources: message?.iodSources || [],
images: message.images || [],
generationInfo: message?.generationInfo,
reasoning_time_taken: message?.reasoning_time_taken

View File

@ -1,14 +1,8 @@
import { saveHistory, saveMessage } from "@/db"
import {
setLastUsedChatModel,
setLastUsedChatSystemPrompt
} from "@/services/model-settings"
import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings"
import { generateTitle } from "@/services/title"
import { ChatHistory } from "@/store/option"
import { updateDialog } from "@/web/iod"
import { AllIodRegistryEntry } from "@/types/iod.ts"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const saveMessageOnError = async ({
e,
history,
@ -68,7 +62,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
getDefaultIodSources(),
[],
1,
message_type
)
@ -80,16 +74,13 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
getDefaultIodSources(),
[],
2,
message_type
)
await setLastUsedChatModel(historyId, selectedModel)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, {
prompt_content,
prompt_id
})
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
}
} else {
const title = await generateTitle(selectedModel, userMessage, userMessage)
@ -102,7 +93,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
getDefaultIodSources(),
[],
1,
message_type
)
@ -114,17 +105,14 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
getDefaultIodSources(),
[],
2,
message_type
)
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(newHistoryId.id, {
prompt_content,
prompt_id
})
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
}
}
@ -145,8 +133,7 @@ export const saveMessageOnSuccess = async ({
webSources,
iodSources,
message_source = "web-ui",
message_type,
generationInfo,
message_type, generationInfo,
prompt_id,
prompt_content,
reasoning_time_taken = 0
@ -159,15 +146,15 @@ export const saveMessageOnSuccess = async ({
image: string
fullText: string
webSources: any[]
iodSources: AllIodRegistryEntry
message_source?: "copilot" | "web-ui"
iodSources: any[]
message_source?: "copilot" | "web-ui",
message_type?: string
generationInfo?: any
prompt_id?: string
prompt_content?: string
reasoning_time_taken?: number
}) => {
var botMessage
var botMessage;
if (historyId) {
if (!isRegenerate) {
await saveMessage(
@ -177,7 +164,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
getDefaultIodSources(),
[],
1,
message_type,
generationInfo,
@ -200,10 +187,7 @@ export const saveMessageOnSuccess = async ({
updateDialog(historyId, botMessage)
await setLastUsedChatModel(historyId, selectedModel!)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, {
prompt_content,
prompt_id
})
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
}
} else {
const title = await generateTitle(selectedModel, message, message)
@ -215,7 +199,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
getDefaultIodSources(),
[],
1,
message_type,
generationInfo,
@ -238,10 +222,7 @@ export const saveMessageOnSuccess = async ({
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel!)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(newHistoryId.id, {
prompt_content,
prompt_id
})
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
}
}
}

View File

@ -11,6 +11,7 @@ import { useStoreMessageOption, type Message } from "~/store/option"
import { useStoreMessage } from "~/store"
import { SystemMessage } from "@langchain/core/messages"
import { getDataFromCurrentTab } from "~/libs/get-html"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { memoryEmbedding } from "@/utils/memory-embeddings"
import { ChatHistory } from "@/store/option"
import {
@ -41,8 +42,6 @@ import {
mergeReasoningContent,
removeReasoning
} from "@/libs/reasoning"
import { AllIodRegistryEntry } from "@/types/iod.ts"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const useMessage = () => {
const {
@ -189,15 +188,15 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: []
},
{
isBot: true,
name: selectedModel,
message: "",
message: "",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -209,7 +208,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -243,7 +242,6 @@ export const useMessage = () => {
}
isAlreadyExistEmbedding = keepTrackOfEmbedding[websiteUrl]
}
setMessages(newMessage)
const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag()
@ -350,7 +348,14 @@ export const useMessage = () => {
metadata: Record<string, any>
}[] = []
// TODO: update type
let iodSources: AllIodRegistryEntry = getDefaultIodSources()
let iodSources: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4)
@ -618,7 +623,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: []
},
{
@ -626,7 +631,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -638,7 +643,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -802,7 +807,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@ -907,7 +912,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: [image]
},
{
@ -915,7 +920,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -927,7 +932,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1096,7 +1101,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@ -1140,7 +1145,7 @@ export const useMessage = () => {
isRegenerate: boolean,
messages: Message[],
history: ChatHistory,
signal: AbortSignal
signal: AbortSignal,
) => {
const url = await getOllamaURL()
setStreaming(true)
@ -1198,7 +1203,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: [image]
},
{
@ -1206,7 +1211,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1218,7 +1223,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1295,12 +1300,8 @@ export const useMessage = () => {
query = removeReasoning(query)
}
const { prompt, webSources, iodSources } = await getSystemPromptForWeb(
query,
[],
webSearch,
iodSearch
)
const { prompt, webSources, iodSources } =
await getSystemPromptForWeb(query, [], webSearch, iodSearch)
setIsSearchingInternet(false)
// message = message.trim().replaceAll("\n", " ")
@ -1555,7 +1556,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: [image],
messageType: messageType
},
@ -1564,7 +1565,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1576,7 +1577,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1723,7 +1724,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
message_source: "copilot",
message_type: messageType,
generationInfo,
@ -1810,7 +1811,7 @@ export const useMessage = () => {
isRegenerate || false,
messages,
memory || history,
signal
signal,
)
} else {
await normalChatMode(

View File

@ -2,14 +2,15 @@ import React from "react"
import { cleanUrl } from "~/libs/clean-url"
import {
defaultEmbeddingModelForRag,
getOllamaURL,
geWebSearchFollowUpPrompt,
geWebSearchKeywordsPrompt,
getOllamaURL,
promptForRag,
systemPromptForNonRagOption
} from "~/services/ollama"
import type { ChatHistory, Message, MeteringEntry } from "~/store/option"
import { useStoreMessageOption } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option"
import {
deleteChatForEdit,
generateID,
@ -46,20 +47,14 @@ import {
mergeReasoningContent,
removeReasoning
} from "@/libs/reasoning"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const useMessageOption = () => {
const {
controller: abortController,
setController: setAbortController,
iodLoading,
setIodLoading,
currentMessageId,
setCurrentMessageId,
messages,
setMessages,
setMessages
} = usePageAssist()
const {
history,
setHistory,
@ -118,8 +113,6 @@ export const useMessageOption = () => {
setIsProcessing(false)
setStreaming(false)
currentChatModelSettings.reset()
setIodLoading(false)
setCurrentMessageId("")
textareaRef?.current?.focus()
if (defaultInternetSearchOn) {
setWebSearch(true)
@ -202,7 +195,6 @@ export const useMessageOption = () => {
})
let newMessage: Message[] = []
let generateMessageId = generateID()
setCurrentMessageId(generateMessageId)
const meter: MeteringEntry = {
id: generateMessageId,
queryContent: message,
@ -222,15 +214,15 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "",
message: "",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -242,7 +234,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -324,9 +316,7 @@ export const useMessageOption = () => {
// Currently only IoD search use keywords
if (iodSearch) {
// Extract keywords
console.log(
"query:" + query + " --> " + JSON.stringify(tokenizeInput(query))
)
console.log("query:"+query+" --> "+JSON.stringify(tokenizeInput(query)));
keywords = tokenizeInput(query)
/*
const questionPrompt = await geWebSearchKeywordsPrompt()
@ -345,35 +335,15 @@ export const useMessageOption = () => {
*/
}
const {
prompt,
webSources,
iodSources,
iodSearchResults: iodData,
iodTokenCount
} = await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
setIodLoading(false)
const { prompt, webSources, iodSources, iodSearchResults: iodData, iodTokenCount } =
await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
console.log("prompt:\n" + prompt)
setIsSearchingInternet(false)
meter.prompt = prompt
meter.iodKeywords = keywords
meter.iodData = Object.values(iodData).flat()
meter.iodData = iodData
meter.iodTokenCount = iodTokenCount
setMessages((prev) => {
return prev.map((message) => {
if (message.id === generateMessageId) {
return {
...message,
webSources,
iodSources,
}
}
return message
})
})
// message = message.trim().replaceAll("\n", " ")
let humanMessage = await humanMessageFormatter({
@ -541,17 +511,20 @@ export const useMessageOption = () => {
modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model,
relatedDataCount: Object.values(iodData).flat()?.length ?? 0,
relatedDataCount: iodData?.length ?? 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(),
cot,
responseContent: content,
modelResponseContent: fullText
modelResponseContent: fullText,
}
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry
data: currentMeteringEntry,
})
setMeteringEntries(_meteringEntries)
localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries))
@ -680,7 +653,7 @@ export const useMessageOption = () => {
setCurrentMeteringEntry({
loading: true,
data: meter
data: meter,
})
if (!isRegenerate) {
@ -691,7 +664,7 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: [image]
},
{
@ -699,7 +672,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -711,7 +684,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -910,6 +883,7 @@ export const useMessageOption = () => {
setIsProcessing(false)
setStreaming(false)
// Save metering entry
const { cot, content } = responseResolver(fullText)
const currentMeteringEntry = {
@ -922,12 +896,15 @@ export const useMessageOption = () => {
date: chatStartTime.getTime(),
cot,
responseContent: content,
modelResponseContent: fullText
modelResponseContent: fullText,
}
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry
data: currentMeteringEntry,
})
setMeteringEntries(_meteringEntries)
} catch (e) {
@ -1019,7 +996,7 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
images: []
},
{
@ -1027,7 +1004,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1039,7 +1016,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: getDefaultIodSources(),
iodSources: [],
id: generateMessageId
}
]
@ -1342,7 +1319,6 @@ export const useMessageOption = () => {
)
} else {
if (webSearch || iodSearch) {
setIodLoading(iodSearch)
await searchChatMode(
webSearch,
iodSearch,
@ -1459,10 +1435,6 @@ export const useMessageOption = () => {
editMessage,
messages,
setMessages,
iodLoading,
currentMessageId,
setIodLoading,
setCurrentMessageId,
onSubmit,
setStreaming,
streaming,

View File

@ -1,18 +0,0 @@
import { AllIodRegistryEntry } from "@/types/iod.ts"
export const getDefaultIodSources = (): AllIodRegistryEntry => {
return {
data: {
data: [],
total: 0
},
scenario: {
data: [],
total: 0
},
organization: {
data: [],
total: 0
}
}
}

View File

@ -1,51 +0,0 @@
import RocketSvg from "@/assets/icons/rocket.svg"
import BulbSvg from "@/assets/icons/bulb.svg"
import EyeSvg from "@/assets/icons/eye.svg"
import ASvg from "@/assets/icons/a.svg"
import BSvg from "@/assets/icons/b.svg"
import CSvg from "@/assets/icons/c.svg"
import DSvg from "@/assets/icons/d.svg"
import ESvg from "@/assets/icons/e.svg"
import FSvg from "@/assets/icons/f.svg"
export const qaPrompt =[
{
title: "如何开发一个适合超大型城市的碳普惠方法学?",
icon: <img src={RocketSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何开发一个零碳园区的数字化评价系统?",
icon: <img src={BulbSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何开发一个碳定价预测系统?",
icon: <img src={EyeSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "新药临床研究如何提升实验安全性?",
icon: <img src={ASvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何加速新药申报和审批?",
icon: <img src={BSvg} alt="Rocket" className="w-10 my-0" />,
},
{
title: "如何研制与司美格鲁肽相似的新药?",
icon: <img src={CSvg} alt="Rocket" className="w-10 my-0" />,
},
// {
// title: "如何解决固态电池的成本和寿命难题?",
// icon: <img src={DSvg} alt="Rocket" className="w-10 my-0" />,
// },
// {
// title: "如何解决船舶制造中的材料腐蚀难题?",
// icon: <img src={ESvg} alt="Rocket" className="w-10 my-0" />,
// },
// {
// title: "如何解决船舶制造中流体模拟和建模优化难题?",
// icon: <img src={FSvg} alt="Rocket" className="w-10 my-0" />,
// },
].map((item, index) => ({
...item,
id: index.toString(),
}))

View File

@ -7,7 +7,6 @@ import OptionOllamaSettings from "./options-settings-ollama"
import OptionShare from "./option-settings-share"
import OptionKnowledgeBase from "./option-settings-knowledge"
import OptionAbout from "./option-settings-about"
import OptionIodSettings from "./option-settings-iod"
import SidepanelChat from "./sidepanel-chat"
import SidepanelSettings from "./sidepanel-settings"
import OptionRagSettings from "./option-rag"
@ -29,7 +28,6 @@ export const OptionRoutingChrome = () => {
<Route path="/settings/share" element={<OptionShare />} />
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/rag" element={<OptionRagSettings />} />
<Route path="/settings/iod" element={<OptionIodSettings />} />
<Route path="/settings/about" element={<OptionAbout />} />
<Route path="/metering" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} />

View File

@ -15,7 +15,6 @@ const MeteringListDetail = lazy(() => import("./metering-list-detail"))
const OptionShare = lazy(() => import("./option-settings-share"))
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
const OptionAbout = lazy(() => import("./option-settings-about"))
const OptionIodSettings = lazy(() => import("./option-settings-iod"))
const OptionRagSettings = lazy(() => import("./option-rag"))
const OptionOpenAI = lazy(() => import("./option-settings-openai"))
@ -32,7 +31,6 @@ export const OptionRoutingFirefox = () => {
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} />
<Route path="/settings/rag" element={<OptionRagSettings />} />
<Route path="/settings/iod" element={<OptionIodSettings />} />
<Route path="/metering" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} />
</Routes>

View File

@ -1,15 +0,0 @@
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
import OptionLayout from "~/components/Layouts/Layout"
import { IodApp } from "@/components/Option/Settings/iod"
const OptionAbout = () => {
return (
<OptionLayout>
<SettingsLayout>
<IodApp />
</SettingsLayout>
</OptionLayout>
)
}
export default OptionAbout

View File

@ -18,10 +18,6 @@ export type ChatHistory = {
type State = {
messages: Message[]
setMessages: (messages: Message[]) => void
currentMessageId: string
setCurrentMessageId: (messageId: string) => void
iodLoading: boolean
setIodLoading: (iodLoading: boolean) => void
history: ChatHistory
setHistory: (history: ChatHistory) => void
streaming: boolean
@ -57,10 +53,6 @@ type State = {
export const useStoreMessage = create<State>((set) => ({
messages: [],
setMessages: (messages) => set({ messages }),
currentMessageId: "",
setCurrentMessageId: (currentMessageId) => set({ currentMessageId }),
iodLoading: false,
setIodLoading: (iodLoading) => set({ iodLoading }),
history: [],
setHistory: (history) => set({ history }),
streaming: false,

View File

@ -1,6 +1,5 @@
import { Knowledge } from "@/db/knowledge"
import { create } from "zustand"
import { AllIodRegistryEntry } from "@/types/iod.ts"
type WebSearch = {
search_engine: string
@ -16,7 +15,7 @@ export type Message = {
name: string
message: string
webSources: any[]
iodSources: AllIodRegistryEntry
iodSources: any[]
images?: string[]
search?: WebSearch
reasoning_time_taken?: number
@ -148,7 +147,7 @@ export const useStoreMessageOption = create<State>((set) => ({
setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
webSearch: false,
setWebSearch: (webSearch) => set({ webSearch }),
iodSearch: true,
iodSearch: false,
setIodSearch: (iodSearch) => set({ iodSearch }),
isSearchingInternet: false,
setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }),

View File

@ -8,20 +8,4 @@ export type IodRegistryEntry = {
data_space?: string
data_type?:string
traceId?:string
fromRepo?: string
}
export type AllIodRegistryEntry = {
data: {
data: IodRegistryEntry[]
total: number
}
scenario: {
data: IodRegistryEntry[]
total: number
}
organization: {
data: IodRegistryEntry[]
total: number
}
}

View File

@ -1,5 +1,3 @@
import { AllIodRegistryEntry } from "@/types/iod.ts"
type WebSearch = {
search_engine: string
search_url: string
@ -14,7 +12,7 @@ export type Message = {
name: string
message: string
webSources: any[]
iodSources: AllIodRegistryEntry
iodSources: any[]
images?: string[]
search?: WebSearch
messageType?: string

View File

@ -1,87 +1,66 @@
import { cleanUrl } from "@/libs/clean-url"
import { PageAssistHtmlLoader } from "@/loader/html"
import { PageAssistPDFUrlLoader } from "@/loader/pdf-url"
import { getOllamaURL } from "@/services/ollama"
import { pageAssistEmbeddingModel } from "@/models/embedding"
import { defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama"
import {
getIsSimpleInternetSearch,
totalSearchResults
} from "@/services/search"
import { getPageAssistTextSplitter } from "@/utils/text-splitter"
import { Document } from "@langchain/core/documents"
import { AllIodRegistryEntry, IodRegistryEntry } from "~/types/iod"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import type { IodRegistryEntry } from "~/types/iod"
import { PageAssitDatabase } from "@/db"
import exp from "constants"
import { enPOSTag, Segment, useDefault } from "segmentit"
import { getDefaultIodSources } from "@/libs/iod.ts"
const segment = useDefault(new Segment())
import { Segment, useDefault, cnPOSTag, enPOSTag} from 'segmentit';
const segment = useDefault(new Segment());
export const tokenizeInput = function (input: string): string[] {
const words = segment.doSegment(input, { simple: false })
console.log(
words.map(function (word) {
return { w: word.w, p: enPOSTag(word.p) }
})
)
return words.filter((word) => word.w.length > 1).map((word) => word.w)
const words = segment.doSegment(input, { simple: false });
console.log(words.map(function(word){return {w:word.w, p:enPOSTag(word.p)}}) );
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() {
const val = localStorage.getItem("iod-connect")
if (!val) {
return _iodConfig
}
try {
return JSON.parse(val)
} catch {
return _iodConfig
}
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"
}
export const iodConfigLocal = {
gatewayUrl: "tcp://127.0.0.1:21036",
registry: "bdware/Registry",
localRepository: "bdtest.local/myrepo1",
doBrowser: "http://127.0.0.1:21030/SCIDE/SCManager"
"gatewayUrl": "tcp://127.0.0.1:21036",
"registry":"bdware/Registry",
"localRepository":"bdtest.local/myrepo1",
"doBrowser":"http://127.0.0.1:21030/SCIDE/SCManager"
}
function inGrepList(str: string){
return (
"什么|问题|需要|合适|设计|考虑|合作|精度|传感器|最新|研究|药物".indexOf(
str
) != -1
)
return "什么|问题|需要|合适|设计|考虑|合作|精度|传感器|最新|研究|药物".indexOf(str)!=-1;
}
export const makeSearchParamsWithDataType = function (
count: number,
keyword: string | string[],
dataType: string
) {
const iodConfig = getIodConfig()
const searchMode = []
searchMode.push({ key: "data_type", type: "MUST", value: dataType })
if (typeof keyword === "string") {
export const makeSearchParamsWithDataType = function(count: number, keyword: string| string[], dataType: string){
const searchMode = [];
searchMode.push({"key":"data_type", "type":"MUST", "value":dataType})
if (typeof keyword === 'string') {
// 如果 keyword 是字符串,则直接添加一个 searchMode 条目
searchMode.push({
key: "description",
type: "MUST",
value: keyword
})
});
} else if (Array.isArray(keyword)) {
// 如果 keyword 是数组,则为每个元素添加一个 searchMode 条目
keyword.forEach((str) => {
keyword.forEach(str => {
if (!inGrepList(str))
searchMode.push({
key: "description",
type: "SHOULD",
value: str
})
})
});
});
}
return {
action: "executeContract",
@ -106,29 +85,25 @@ export const makeSearchParamsWithDataType = function (
}
}
export const makeRegSearchParams = function (
count: number,
keyword: string | string[]
) {
const searchMode = []
const iodConfig = getIodConfig()
if (typeof keyword === "string") {
export const makeRegSearchParams = function(count: number, keyword: string| string[]){
const searchMode = [];
if (typeof keyword === 'string') {
// 如果 keyword 是字符串,则直接添加一个 searchMode 条目
searchMode.push({
key: "description",
type: "MUST",
value: keyword
})
});
} else if (Array.isArray(keyword)) {
// 如果 keyword 是数组,则为每个元素添加一个 searchMode 条目
keyword.forEach((str) => {
keyword.forEach(str => {
if (!inGrepList(str))
searchMode.push({
key: "description",
type: "SHOULD",
value: str
})
})
});
});
}
return {
action: "executeContract",
@ -153,14 +128,7 @@ export const makeRegSearchParams = function (
}
}
export const makeDOIPParams = (
doId: string,
op: string,
attributes: Object,
requestBody: string
) => {
const iodConfig = getIodConfig()
return {
export const makeDOIPParams = (doId:string, op:string, attributes:Object, requestBody: string) => ({
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
@ -171,35 +139,26 @@ export const makeDOIPParams = (
attributes: attributes,
body: requestBody
}
}
}
})
export const retrieveDoc = function(doId: string) : Promise<Document> {
const iodConfig = getIodConfig()
console.log("retriveDoc:"+doId)
const params = makeDOIPParams(
doId,
"Retrieve",
{
const params = makeDOIPParams(doId,"Retrieve",{
bodyBase64Encoded: false
},
""
)
}, "");
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
})
.then((response) => {
console.log("responseIn retrieveDoc:")
console.log(response)
return response.json()
})
}).then((response) => {
console.log("responseIn retrieveDoc:");
console.log(response);
return response.json()})
.then((res) => {
console.log("res:")
console.log(res.result.body)
console.log("res:");
console.log(res.result.body);
//TODO
return {
metadata:{traceId:res.result.header.attributes?.traceId},
@ -208,81 +167,63 @@ export const retrieveDoc = function (doId: string): Promise<Document> {
})
}
export const updateInLocalRepo = function (
historyId: string,
requestBody: Object
): Promise<string> {
const iodConfig = getIodConfig()
const params = makeDOIPParams(
iodConfig.localRepository,
"Update",
{
aiDialogID: historyId,
export const updateInLocalRepo = function(historyId: string, requestBody: Object) : Promise<string> {
const params = makeDOIPParams(iodConfig.localRepository,"Update",{
"aiDialogID": historyId,
bodyBase64Encoded: false
},
JSON.stringify(requestBody)
)
}, JSON.stringify(requestBody));
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
return fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
})
.then((response) => response.json())
}).then((response) => response.json())
.then((res) => {
console.log("update dialog:"+JSON.stringify(res))
return res.body
return res.body;
})
}
export const updateDialog = async function (
histroyId: string,
botMessage: any
): Promise<string> {
export const updateDialog = async function(histroyId : string, botMessage: any): Promise<string> {
//TODO @Nex confused by Message/MessageType in ./db/index.ts!
const db = new PageAssitDatabase()
const chatHistory = await db.getChatHistory(histroyId)
var userMessage = null
var userMessage = null;
for (var i=0;i<chatHistory.length;i++){
userMessage = chatHistory[i]
if (userMessage.role == "user") break
userMessage = chatHistory[i];
if (userMessage.role=='user') break;
}
let updateBody: any = {}
let updateBody:any = {};
// !!!IMPORTANT!!! traceId = histroyId+"/"+userMessage.id;
// Update traceId in retrieveDoc!
updateBody.traceId = histroyId + "/" + userMessage.id
updateBody.traceId = histroyId+"/"+userMessage.id;
updateBody.question = {
id: histroyId + "/" + userMessage.id,
content: userMessage.content,
tokenCount: userMessage.content.length
"id": histroyId+"/"+userMessage.id,
"content": userMessage.content,
"tokenCount": userMessage.content.length
}
updateBody.answer = {
id: histroyId + "/" + botMessage.id,
content: botMessage.content,
tokenCount: botMessage.content.length
"id": histroyId+"/"+botMessage.id,
"content": botMessage.content,
"tokenCount": botMessage.content.length
}
//TODO set a correct model ID
updateBody.model = { id: "bdware.ollama/" + userMessage.name }
updateBody.model = {"id":"bdware.ollama/" + userMessage.name}
//TODO incorrect tokenCount calculated!!
updateBody.webSources =
botMessage.webSources?.map((r) => ({
updateBody.webSources = botMessage.webSources?.map((r) => ({
url: r.url,
tokenCount: r.url.length,
content: r.url,
traceId: r?.traceId
})) ?? []
updateBody.IoDSources =
botMessage.iodSources?.map((r) => ({
})) ?? [];
updateBody.IoDSources = botMessage.iodSources?.map((r) => ({
id: r.doId,
tokenCount:
r.content || r.description
? calculateTokenCount(r.content || r.description)
: 0,
tokenCount: (r.content || r.description)?calculateTokenCount((r.content || r.description)):0,
content: r.content || r.description,
traceId: r?.traceId
})) ?? []
console.log("updateBody:")
})) ?? [];
console.log("updateBody:");
console.log(updateBody)
return updateInLocalRepo(histroyId,updateBody)
}
@ -290,112 +231,66 @@ export const updateDialog = async function (
export async function localIodSearch(
query: string,
keywords: string[]
): Promise<AllIodRegistryEntry> {
const iodConfig = getIodConfig()
): Promise<IodRegistryEntry[]> {
const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keywords)
const dataParams = makeSearchParamsWithDataType(
TOTAL_SEARCH_RESULTS,
keywords,
"data"
)
const scenarioParams = makeSearchParamsWithDataType(
TOTAL_SEARCH_RESULTS,
keywords,
"scenario"
)
const orgParams = makeSearchParamsWithDataType(
TOTAL_SEARCH_RESULTS,
keywords,
"organization"
)
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 10000);
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keywords);
const dataParams = makeSearchParamsWithDataType(TOTAL_SEARCH_RESULTS,keywords, "data");
const scenarioParams = makeSearchParamsWithDataType(TOTAL_SEARCH_RESULTS,keywords, "scenario");
const orgParams = makeSearchParamsWithDataType(TOTAL_SEARCH_RESULTS,keywords, "organization");
try {
console.log("params------->", params)
console.log('params------->',params)
const requests = [
fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(dataParams),
signal: abortController.signal
}),
fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(scenarioParams),
signal: abortController.signal
}),
fetch(iodConfig.doBrowser, {
method: "POST",
body: JSON.stringify(orgParams),
signal: abortController.signal
})
]
fetch(iodConfig.doBrowser,{method: "POST", body: JSON.stringify(dataParams),signal: abortController.signal}),
fetch(iodConfig.doBrowser,{method: "POST", body: JSON.stringify(scenarioParams),signal: abortController.signal}),
fetch(iodConfig.doBrowser,{method: "POST", body: JSON.stringify(orgParams),signal: abortController.signal})
];
//TODO @Zhaoweijie 这三类分别是数据、场景、团队的搜索请求。
const responses = await Promise.all(requests)
const results = await Promise.all(responses.map((res) => res.json()))
const allResults: AllIodRegistryEntry = getDefaultIodSources()
let i = 0
const responses = await Promise.all(requests);
const results = await Promise.all(responses.map(res => res.json()));
const allResults: IodRegistryEntry[] = [];
for (const res of results) {
// 检查顶层状态
if (res.status !== "Success") {
continue // 跳过失败的请求
continue; // 跳过失败的请求
}
let body
let body;
try {
body = JSON.parse(res.result.body)
body = JSON.parse(res.result.body);
} catch (e) {
console.warn("Failed to parse result.body as JSON", e)
continue
console.warn("Failed to parse result.body as JSON", e);
continue;
}
if (body.code !== 0) {
continue
continue;
}
const entries: IodRegistryEntry[] = body.data?.results || []
const prunedEntries: IodRegistryEntry[] = []
const seenDoIds = new Set<string>()
const entries: IodRegistryEntry[] = body.data?.results || [];
// 数据清洗:补全 url 和 doId
for (const r of entries) {
r.url = r.url || r.pdf_url
// @ts-ignore
r.doId = r.doId || r.doid
if (seenDoIds.has(r.doId)) {
continue
r.url = r.url || r.pdf_url;
r.doId = r.doId || r.doid;
}
prunedEntries.push(r)
// 合并到总结果
allResults.push(...entries);
}
// 数据
if (i === 0) {
allResults.data = {
data: prunedEntries,
total: body.data?.total ?? 0
const seenDoIds = new Set<string>();
const prunedResults: IodRegistryEntry[] = [];
for (const r of allResults) {
if (r.doId && !seenDoIds.has(r.doId)) {
seenDoIds.add(r.doId);
prunedResults.push(r);
}
}
// 场景
if (i === 1) {
allResults.scenario = {
data: prunedEntries,
total: body.data?.total ?? 0
}
}
// 团队
if (i === 2) {
allResults.organization = {
data: prunedEntries,
total: body.data?.total ?? 0
}
}
i++
}
return allResults
return prunedResults;
} catch (e) {
console.log(e)
return getDefaultIodSources()
console.log(e);
return [];
}
}
const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\//
@ -405,41 +300,28 @@ export const searchIod = async (query: string, keywords: string[]) => {
const searchResults = await localIodSearch(query, keywords)
const isSimpleMode = await getIsSimpleInternetSearch()
console.log(
"searchMode:" +
isSimpleMode +
"\n kw:" +
JSON.stringify(keywords) +
"\n" +
" ->searchResult:\n" +
JSON.stringify(searchResults)
)
console.log("searchMode:"+isSimpleMode+"\n kw:"+JSON.stringify(keywords)+"\n"+" ->searchResult:\n"+JSON.stringify(searchResults))
console.log("pruned Search Result:"+JSON.stringify(searchResults.map(r=>r.doId+" "+r.name)))
if (isSimpleMode) {
await getOllamaURL()
return searchResults
}
const docs: Document<Record<string, any>>[] = []
for (const result of Object.values(searchResults)
.map((item) => item.data)
.flat()) {
const resMap = new Map<string, IodRegistryEntry>()
for (const result of searchResults) {
const url = result.url
if (result.doId){
//TODO !!!!@Nex traceId should be the id of history/question!
let docFromRetrieve = await retrieveDoc(result.doId)
console.log(
"doc from Retrieve:" +
result.doId +
" -->" +
JSON.stringify(docFromRetrieve)
)
let docFromRetrieve = await retrieveDoc(result.doId);
console.log("doc from Retrieve:"+result.doId+" -->"+JSON.stringify(docFromRetrieve))
docs.push(docFromRetrieve)
result.description = docFromRetrieve.pageContent
result.traceId = docFromRetrieve.metadata?.traceId
continue
result.description = docFromRetrieve.pageContent;
result.traceId = docFromRetrieve.metadata?.traceId;
continue;
}
if (!url) {
continue
continue;
}
let htmlUrl = ""
@ -538,6 +420,7 @@ export const searchIod = async (query: string, keywords: string[]) => {
}
export const calculateTokenCount = function(str:string){
const byteArray = new TextEncoder().encode(str)
return byteArray.length
const byteArray = new TextEncoder().encode(str);
return byteArray.length;
}

File diff suppressed because one or more lines are too long