feat/playground: 重构 playground组件

- 更新 Data 和 History组件的样式和布局
- 添加新的功能和交互,如热门搜索和智能体选择
- 优化组件性能和可维护性
This commit is contained in:
zhaoweijie 2025-08-21 14:08:07 +08:00
parent df0bf51bdf
commit efbf2a3eff
19 changed files with 1009 additions and 709 deletions

View File

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

View File

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

View File

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

View File

@ -16,28 +16,28 @@ export const PlaygroundData = () => {
description: description:
"数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心", "数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心",
time: "包括2019年8月2021年8月和2024年6月", time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第" metadata: "热 榜 第2"
}, },
{ {
title: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023", title: "祁连山老虎沟大本营10米气象每日值数据集V1.02018-2023",
description: description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新", "中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月", time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第" metadata: "热 榜 第2"
}, },
{ {
title: "李嘉图为研究老虎沟大本营2014-2018年...", title: "李嘉图为研究老虎沟大本营2014-2018年...",
description: description:
"中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新", "中国科学院西北生态环境资源研究院2021年8月3日发布2021年8月3日20:48更新",
time: "包括2019年8月2021年8月和2024年6月", time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第" metadata: "热 榜 第2"
}, },
{ {
title: "青海玉树B1区俄日矿勘探数据2017-2023", title: "青海玉树B1区俄日矿勘探数据2017-2023",
description: description:
"数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查", "数字中国集团CSTR:3260.11.1528414774204895456DT2023年地质勘探补充调查",
time: "包括2019年8月2021年8月和2024年6月", time: "包括2019年8月2021年8月和2024年6月",
metadata: "热 榜 第" metadata: "热 榜 第2"
} }
] ]
@ -60,27 +60,26 @@ export const PlaygroundData = () => {
} }
return ( return (
<div className="flex flex-col h-full overflow-y-hidden"> <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">
{/* 数据导航 */} {/* 数据导航 */}
<DataNavigation <DataNavigation
Header={ Header={
<div className="flex items-center text-[#1d4eD8] gap-1"> <div className="flex items-center gap-0.5 text-[#3480e3]">
<svg <svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" p-id="3572"
viewBox="0 0 32 32" width="18"
width="20" height="18">
height="20">
<defs></defs>
<g>
<circle cx="22" cy="24" r="2" fill="rgb(29, 78, 216)"></circle>
<path <path
fill="rgb(29, 78, 216)" 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"
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> p-id="3573"
<path fill="#3480e3"></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> </svg>
</div> </div>
@ -91,37 +90,29 @@ export const PlaygroundData = () => {
{/* 数据列表 */} {/* 数据列表 */}
<div className="space-y-1.5 flex-1 overflow-y-auto"> <div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0, 3).map((item, index) => ( {data.slice(0, 3).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-3 h-[148px]"> <Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2"> <h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title} {item.title}
</h3> </h3>
<p className="text-xs text-gray-500 line-clamp-2"> <p className="text-sm text-[#383838] line-clamp-2">
{item.description} {item.description}
</p> </p>
<p className="text-gray-700 truncate">{item.time}</p> <p className="text-[#828282] text-xs truncate">{item.time}</p>
{item.metadata && ( {item.metadata && (
<div className="text-green-500 text-xs px-2 py-1 rounded-full flex items-center gap-1"> <div>
<svg <span className="inline-block text-[#D90000] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
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} {item.metadata}
</span>
</div> </div>
)} )}
</div> </div>
</Card> </Card>
))} ))}
</div> </div>
</div>
{/* 抽屉 */} {/* 抽屉 */}
<Drawer <Drawer
@ -129,47 +120,31 @@ export const PlaygroundData = () => {
closable={{ "aria-label": "Close Button" }} closable={{ "aria-label": "Close Button" }}
onClose={onClose} onClose={onClose}
open={open} open={open}
width={600}> width="33.33%">
<List <div className="grid grid-cols-1 gap-3 overflow-y-auto">
itemLayout="vertical" {data.slice(0, 3).map((item, index) => (
dataSource={data} <Card
renderItem={(item, index) => ( key={index}
<List.Item> hoverable
<List.Item.Meta className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
title={ <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900"> <h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title} {item.title}
</h3> </h3>
} <p className="text-sm text-[#383838]">{item.description}</p>
description={ <p className="text-[#828282] text-xs">{item.time}</p>
<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 && ( {item.metadata && (
<div className="text-green-500 text-xs px-2 py-1 rounded-full flex items-center gap-1"> <div>
<svg <span className="inline-block text-[#D90000] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
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} {item.metadata}
</span>
</div> </div>
)} )}
</div> </div>
} </Card>
/> ))}
</List.Item> </div>
)}
/>
</Drawer> </Drawer>
</div> </Card>
) )
} }

View File

@ -1,13 +1,45 @@
import { Sidebar } from "@/components/Option/Sidebar.tsx" import { Sidebar } from "@/components/Option/Sidebar.tsx"
import React, { useContext, useState } from "react" import React, { useContext, useMemo, useState } from "react"
import { useMessageOption } from "@/hooks/useMessageOption.tsx" import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useStoreChatModelSettings } from "@/store/model.tsx" import { useStoreChatModelSettings } from "@/store/model.tsx"
import { Card, Tooltip } from "antd" import {
Button,
Card,
Divider,
List,
Menu,
MenuProps,
Popover,
Select,
Tooltip
} from "antd"
import { PageAssitDatabase } from "@/db" import { PageAssitDatabase } from "@/db"
import { EraserIcon } from "lucide-react" import { EraserIcon, PanelLeftIcon } from "lucide-react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useQueryClient } from "@tanstack/react-query" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { HistoryContext } from "@/components/Layouts/Layout.tsx" 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 = () => { export const PlaygroundHistory = () => {
const { setSystemPrompt } = useStoreChatModelSettings() const { setSystemPrompt } = useStoreChatModelSettings()
@ -20,6 +52,7 @@ export const PlaygroundHistory = () => {
setHistoryId, setHistoryId,
historyId, historyId,
clearChat, clearChat,
selectedModel,
setSelectedModel, setSelectedModel,
temporaryChat, temporaryChat,
setSelectedSystemPrompt setSelectedSystemPrompt
@ -29,13 +62,163 @@ export const PlaygroundHistory = () => {
const queryClient = useQueryClient() 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 ( return (
<Card <Card
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`} 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={{ paddingTop: "4rem", width: show ? "300px" : "0" }} style={{ width: show ? "300px" : "0" }}>
title={ {/*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}>
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
{t("sidebarTitle")} <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>
<Tooltip <Tooltip
title={t("settings:generalSettings.system.deleteChatHistory.label")} title={t("settings:generalSettings.system.deleteChatHistory.label")}
placement="right"> placement="right">
@ -59,8 +242,7 @@ export const PlaygroundHistory = () => {
</button> </button>
</Tooltip> </Tooltip>
</div> </div>
}> <div className="overflow-y-auto flex-1 pl-7">
<div className="overflow-y-auto">
<Sidebar <Sidebar
onClose={() => setShow(true)} onClose={() => setShow(true)}
setMessages={setMessages} setMessages={setMessages}
@ -75,6 +257,7 @@ export const PlaygroundHistory = () => {
history={history} history={history}
/> />
</div> </div>
</div>
</Card> </Card>
) )
} }

View File

@ -1,7 +1,47 @@
import React from 'react'; import React from "react"
import { Button, 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 }>`
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;
`
const SuccessIcon = () => { const SuccessIcon = () => {
return (<svg return (
<svg
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -13,49 +53,67 @@ const SuccessIcon = () => {
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
</svg>) </svg>
)
} }
const LoadingIcon = () => { const LoadingIcon = () => {
return ( return (
<svg <svg
className="icon animate-spin"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" p-id="8408"
viewBox="0 0 24 24"
width="18" width="18"
height="18"> height="18">
<defs></defs>
<g>
<path <path
fill="rgb(59, 130, 246)" d="M512 128C299.936 128 128 296.672 128 504.736c0 130.784 67.904 245.984 170.976 313.536l35.52-52.256C248.576 709.696 192 613.696 192 504.736c0-173.376 143.264-313.92 320-313.92s320 140.544 320 313.92c0 98.112-45.856 185.696-117.696 243.296l-73.792-72.416V864h192l-72.768-71.36C843.072 723.52 896 620.16 896 504.704 896 296.672 724.064 128 512 128z"
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> fill="#52c41a"
</g> p-id="8409"></path>
</svg> </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 = () => { export const PlaygroundIodRelevant: React.FC = () => {
const data = [ const data = [
{ {
title: "已在29个科学数据中心的50万个科学数据集中进行搜索", title: <p><span className="text-[#9d0000]">29</span><span className="text-[#9d0000]">50</span></p>,
description: <p><span className="text-green-700"> 4 </span></p>,
status: "success"
},
{
title: <p><span className="text-[#9d0000]">100</span><span className="text-[#9d0000]">2800</span></p>,
description: "已发现4个数据集", description: "已发现4个数据集",
status: "success" status: "success"
}, },
{ {
title: "已在100万篇论文、2800个科创场景中进行搜索", title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span></p>,
description: "已发现4个数据集",
status: "success"
},
{
title: "正在1000位智库专家、12万个创新机构中进行搜索",
status: "loading" status: "loading"
}, }
] ]
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
data.push({ data.push({
title: "已在29个科学数据中心的50万个科学数据集中进行搜索" + i, title: <p><span className="text-[#9d0000]">1000</span><span className="text-[#9d0000]">12</span>{i}</p>,
description: "已发现4个数据集", description: "已发现4个数据集",
status: "success" status: "success"
}) })
@ -63,33 +121,65 @@ export const PlaygroundIodRelevant: React.FC = () => {
return ( return (
<div className="flex flex-col h-full"> <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} />
<CircleElement delay={1} />
<CircleElement delay={2} />
</div>
</div>
{/* Header */} {/* Header */}
<div className="flex justify-between items-center mb-4"> <div className="p-3">
<h2 className="text-lg font-semibold text-gray-900"> <h2 className="text-xl font-semibold text-[#08307f] flex justify-between 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>
</h2> </h2>
<span className="text-sm text-blue-600 font-medium">{data.length}</span> <p className="text-sm text-[#08307f] mt-1 align-middle">
</p>
</div> </div>
{/* Content */} {/* Content */}
<div className="space-y-3 flex-1 overflow-y-auto"> <div className="space-y-2 flex-1 overflow-y-auto">
{ {data.map((item, index) => (
data.map((item, index) => ( <Card
<div className="flex items-start space-x-3"> className="[&>*:first-child]:!p-3 shadow-md"
key={index}
>
<div className="flex items-start gap-2">
<div className="w-5 h-5 mt-1 flex-shrink-0"> <div className="w-5 h-5 mt-1 flex-shrink-0">
{item.status === "success" ? <SuccessIcon /> : <LoadingIcon />} {item.status === "success" ? (
<SuccessIcon />
) : (
<LoadingIcon />
)}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<p className="text-sm text-gray-700"> <p className="text-sm text-gray-700">{item.title}</p>
{item.title} {item.description && (
<p className="text-xs text-gray-500 mt-1">
{item.description}
</p> </p>
{item.description && <p className="text-xs text-gray-500 mt-1">{item.description}</p>} )}
</div> </div>
</div> </div>
)) </Card>
} ))}
</div> </div>
</div> </div>
</Card>
) )
} }

View File

@ -60,12 +60,12 @@ export const PlaygroundMessage = (props: Props) => {
return ( 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="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="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-4 md:gap-6 my-2 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-end"> <div className="w-8 flex flex-col relative items-center justify-center bottom-[8px]">
<div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r"> <div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center text-opacity-100r">
{props.isBot ? ( {props.isBot ? (
!props.botAvatar ? ( !props.botAvatar ? (
<div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div> <div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400 hidden"></div>
) : ( ) : (
props.botAvatar props.botAvatar
) )
@ -78,13 +78,6 @@ export const PlaygroundMessage = (props: Props) => {
</div> </div>
<div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]"> <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 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> </span>
{props.isBot && {props.isBot &&
@ -99,7 +92,7 @@ export const PlaygroundMessage = (props: Props) => {
</Tag> </Tag>
)} )}
</div> </div>
<div className="flex flex-grow flex-col"> <div className={`flex flex-grow flex-col`}>
{!editMode ? ( {!editMode ? (
props.isBot ? ( props.isBot ? (
<> <>
@ -140,9 +133,10 @@ export const PlaygroundMessage = (props: Props) => {
<p <p
className={`prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${ className={`prose-lg dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
props.message_type && props.message_type &&
"italic text-gray-500 dark:text-gray-400 text-sm" "italic dark:text-gray-400"
}`}> } flex flex-row-reverse`}>
{props.message} {props.message}
{/*<span className="bg-[#0000000a] inline-block py-[9px] text-[#000000d9] rounded-xl px-4 font-light text-sm">{props.message}</span>*/}
</p> </p>
) )
) : ( ) : (

View File

@ -47,23 +47,26 @@ export const PlaygroundScene = () => {
} }
return ( return (
<div className="h-full overflow-y-hidden flex flex-col"> <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">
{/* 数据导航 */} {/* 数据导航 */}
<DataNavigation <DataNavigation
Header={ Header={
<div className="flex items-center text-[#15803d] gap-1"> <div className="flex items-center text-[#52c41a] gap-1">
<svg <svg
className="icon"
viewBox="0 0 1025 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" p-id="6235"
viewBox="0 0 32 32" width="18"
width="20" height="18">
height="20">
<defs></defs>
<g>
<path <path
fill="rgb(21, 128, 61)" 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"
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> fill="#52c41a"
</g> p-id="6236"></path>
</svg> </svg>
</div> </div>
@ -73,28 +76,33 @@ export const PlaygroundScene = () => {
{/* 场景列表 */} {/* 场景列表 */}
<div className="space-y-1.5 flex-1 overflow-y-auto"> <div className="space-y-1.5 flex-1 overflow-y-auto">
{data.slice(0,3).map((item, index) => ( {data.slice(0, 3).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-4 h-[148px]" > <Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2"> <h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title} {item.title}
</h3> </h3>
<p className="flex items-center gap-1.5"> <span className="text-sm text-[#383838] line-clamp-2">
<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} {item.description}
</span> </span>
<p className="text-[#828282] text-xs truncate">
{item.demander}
</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#003AD4] bg-[#003ad430] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#00BF68] bg-[#00bf6830] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div> </div>
</Card> </Card>
))} ))}
</div> </div>
</div>
{/* 抽屉 */} {/* 抽屉 */}
<Drawer <Drawer
@ -102,41 +110,34 @@ export const PlaygroundScene = () => {
closable={{ "aria-label": "Close Button" }} closable={{ "aria-label": "Close Button" }}
onClose={onClose} onClose={onClose}
open={open} open={open}
width={600}> width="33.33%">
<List <div className="grid grid-cols-1 gap-3 overflow-y-auto">
itemLayout="vertical" {data.slice(0, 3).map((item, index) => (
dataSource={data} <Card
renderItem={(item, index) => ( key={index}
<List.Item> hoverable
<List.Item.Meta className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
title={ <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900"> <h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title} {item.title}
</h3> </h3>
} <span className="text-sm text-[#383838]">
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} {item.description}
</span> </span>
<p className="text-[#828282] text-xs">{item.demander}</p>
<p className="flex items-center gap-1.5">
<span className="inline-block text-[#003AD4] bg-[#003ad430] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
<span className="inline-block text-[#00BF68] bg-[#00bf6830] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span>
</p>
</div>
</Card>
))}
</div> </div>
}
/>
</List.Item>
)}
/>
</Drawer> </Drawer>
</div> </Card>
) )
} }

View File

@ -47,23 +47,30 @@ export const PlaygroundTeam = () => {
} }
return ( return (
<div className="h-full overflow-y-hidden flex flex-col"> <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">
{/* 数据导航 */} {/* 数据导航 */}
<DataNavigation <DataNavigation
Header={ Header={
<div className="flex items-center text-[#15803d] gap-1"> <div className="flex items-center text-[#BE0BAC] gap-1">
<svg <svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon" p-id="7272"
viewBox="0 0 32 32" width="18"
width="20" height="18">
height="20">
<defs></defs>
<g>
<path <path
fill="rgb(21, 128, 61)" 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"
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> p-id="7273"
</g> 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>
</svg> </svg>
</div> </div>
@ -72,26 +79,31 @@ export const PlaygroundTeam = () => {
/> />
{/* 场景列表 */} {/* 场景列表 */}
<div className="grid grid-cols-2 gap-3 flex-1 overflow-y-auto"> <div className="grid grid-cols-3 gap-3 flex-1 overflow-y-auto">
{data.slice(0,2).map((item, index) => ( {data.slice(0, 5).map((item, index) => (
<Card key={index} hoverable className="[&>*:first-child]:!p-3" > <Card
key={index}
className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900 line-clamp-2"> <h3 className="text-base font-medium mb-1 text-[#222222] line-clamp-2">
{item.title} {item.title}
</h3> </h3>
<p className="text-[#828282] text-xs truncate">
{item.description}
</p>
<p className="flex items-center gap-1.5"> <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 className="inline-block text-[#BE0BAC] bg-[#be0bac30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span> </span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full"> <span className="inline-block text-[#EB1C1C] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span> </span>
</p> </p>
<p className="text-xs text-gray-500 line-clamp-2">{item.description}</p>
</div> </div>
</Card> </Card>
))} ))}
</div> </div>
</div>
{/* 抽屉 */} {/* 抽屉 */}
<Drawer <Drawer
@ -99,38 +111,31 @@ export const PlaygroundTeam = () => {
closable={{ "aria-label": "Close Button" }} closable={{ "aria-label": "Close Button" }}
onClose={onClose} onClose={onClose}
open={open} open={open}
width={600}> width="33.33%">
<List <div className="grid grid-cols-1 gap-3 overflow-y-auto">
itemLayout="vertical" {data.slice(0, 5).map((item, index) => (
dataSource={data} <Card
renderItem={(item, index) => ( key={index}
<List.Item> hoverable
<List.Item.Meta className="[&_.ant-card-body]:!p-2 !bg-[gb(248, 248, 248)] border !border-[#e9e9e9]">
title={ <div className="flex flex-col gap-0.5">
<h3 className="text-sm font-medium text-gray-900"> <h3 className="text-base font-medium mb-1 text-[#222222]">
{item.title} {item.title}
</h3> </h3>
} <p className="text-[#828282] text-xs">{item.description}</p>
description={
<div className="space-y-1">
<p className="flex items-center gap-1.5"> <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 className="inline-block text-[#BE0BAC] bg-[#be0bac30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span> </span>
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full"> <span className="inline-block text-[#EB1C1C] bg-[#eb1c1c30] h-5 leading-5 mt-1 text-xs rounded-full px-2.5">
</span> </span>
</p> </p>
<p className="text-xs text-gray-500">
{item.description}
</p>
</div> </div>
} </Card>
/> ))}
</List.Item> </div>
)}
/>
</Drawer> </Drawer>
</div> </Card>
) )
} }

View File

@ -1,267 +1,66 @@
import { useStorage } from "@plasmohq/storage/hook" import React, { useContext } from "react"
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 { PageAssistSelect } from "../Select"
import { MoreOptions } from "./MoreOptions"
import { useContext } from "react"
import { HistoryContext } from "@/components/Layouts/Layout.tsx" import { HistoryContext } from "@/components/Layouts/Layout.tsx"
type Props = { import { PanelLeftIcon } from "lucide-react"
sidebarOpen: boolean import { Button } from "antd"
setSidebarOpen: () => void import { PlusOutlined } from "@ant-design/icons"
setOpenModelSettings: (open: boolean) => void import { useMessageOption } from "@/hooks/useMessageOption.tsx"
} import { useTranslation } from "react-i18next"
export const Header: React.FC<Props> = ({ export const Header = () => {
setOpenModelSettings, const { show, setShow } = useContext(HistoryContext)
setSidebarOpen,
sidebarOpen
}) => {
const { t, i18n } = useTranslation(["option", "common"])
const isRTL = i18n?.dir() === "rtl"
const { t } = useTranslation(["option", "common", "settings"])
const { clearChat } = useMessageOption()
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)
}
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 ( return (
<div <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 ${ className={`w-full h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${show ? "left-[300px]" : ""}`}>
temporaryChat && "!bg-gray-200 dark:!bg-black" {/*控制侧边栏显示隐藏与新建对话*/}
}`}> {!show && (
<div className="flex gap-2 items-center"> <div className="flex items-center gap-3">
{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" }}>
{t("projectTitle")}
</h2>
<button <button
className="text-gray-500 dark:text-gray-400" className="text-gray-500 dark:text-gray-400"
onClick={() => setSidebarOpen()}> onClick={() => {
setShow(!show)
}}>
<PanelLeftIcon className="w-6 h-6" /> <PanelLeftIcon className="w-6 h-6" />
</button> </button>
</div> <Button
<NewChat clearChat={clearChat} /> color="cyan"
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600"> variant="filled"
{"/"} shape="round"
</span> style={{
<div className="hidden lg:block"> color: "#0057ff",
<Select background: "#0057ff0f",
className="w-80" border: "1px solid #0066ff26"
placeholder={t("common:selectAModel")}
// loadingText={t("common:selectAModel")}
value={selectedModel}
onChange={(e) => {
setSelectedModel(e)
localStorage.setItem("selectedModel", e)
}} }}
filterOption={(input, option) => { onClick={clearChat}>
//@ts-ignore <div className="flex items-center justify-between w-full">
return ( <div className="flex items-center">
option?.label?.props["data-title"] <PlusOutlined
?.toLowerCase() className="text-sm"
?.indexOf(input.toLowerCase()) >= 0 style={{ fontSize: "16px", fontWeight: 500 }}
)
}}
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()
// }}
/> />
<span>{t("newChat")}</span>
</div> </div>
<div className="lg:hidden">
<ModelSelect />
</div> </div>
<span className="text-lg font-thin text-zinc-300 dark:text-zinc-600"> </Button>
{"/"} </div>
</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> <div
), className={`
value: prompt.id absolute left-1/2 transform -translate-x-1/2
}))} w-[600px] h-[55px] bg-white dark:bg-black
/> flex items-center justify-center
</div> shadow-[0px_0px_5px_rgba(0,0,0,0.2)]
<div className="lg:hidden"> rounded-b-[15px]
<PromptSelect transition-[top]
selectedSystemPrompt={selectedSystemPrompt} ${show ? "-top-[56px]" : "-top-[1px] delay-200"}
setSelectedSystemPrompt={setSelectedSystemPrompt} `}>
setSelectedQuickPrompt={setSelectedQuickPrompt} <h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
/> <span className="text-[#d30100]"></span>
</div> </h2>
<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>
</div> </div>
) )

View File

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

View File

@ -0,0 +1,264 @@
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

@ -20,10 +20,8 @@ import { useStoreChatModelSettings } from "@/store/model"
import { useSmartScroll } from "@/hooks/useSmartScroll" import { useSmartScroll } from "@/hooks/useSmartScroll"
import { ChevronDown } from "lucide-react" import { ChevronDown } from "lucide-react"
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx" 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 { PlaygroundHistory } from "@/components/Common/Playground/History.tsx"
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx" import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
export const Playground = () => { export const Playground = () => {
@ -148,13 +146,13 @@ export const Playground = () => {
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : "" dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
} bg-white dark:bg-[#171717]`}> } bg-white dark:bg-[#171717]`}>
<PlaygroundHistory /> <PlaygroundHistory />
<div className="relative h-full flex-1 prose-lg flex justify-center [&>*]:max-w-[848px]"> <div className="relative h-full flex-1 prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
<div <div
ref={containerRef} ref={containerRef}
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"> className="custom-scrollbar flex h-auto w-full flex-col items-center overflow-x-hidden overflow-y-auto px-5">
<PlaygroundChat /> <PlaygroundChat />
</div> </div>
<div className="absolute bottom-0 w-full"> <div className="relative bottom-0 w-full">
{!isAtBottom && ( {!isAtBottom && (
<div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center"> <div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center">
<button <button
@ -170,16 +168,16 @@ export const Playground = () => {
{/*auto_530px_165px*/} {/*auto_530px_165px*/}
{messages.length && ( {messages.length && (
<div <div
className="w-1/4 h-full grid grid-rows-[1fr_2fr_175px] pt-16 pr-5 pb-0 border-l border-gray-200" className="w-4/12 h-full grid grid-rows-10 gap-3 pt-16 pr-5 pb-0"
style={{ paddingTop: "4rem" }}> style={{ paddingTop: "4rem" }}>
<div className="w-full overflow-y-auto border-gray-200 border-b p-3"> <div className="w-full row-span-4">
<PlaygroundIodRelevant /> <PlaygroundIodRelevant />
</div> </div>
<div className="w-full grid grid-cols-2 gap-3 custom-scrollbar border-gray-200 border-b p-3"> <div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
<PlaygroundData /> <PlaygroundData />
<PlaygroundScene /> <PlaygroundScene />
</div> </div>
<div className="w-full p-3"> <div className="w-full row-span-2 pb-3">
<PlaygroundTeam /> <PlaygroundTeam />
</div> </div>
</div> </div>

View File

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

View File

@ -1,73 +1,14 @@
import { Card, Col, Row } from "antd" 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 { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { useMutation, useQueryClient } from "@tanstack/react-query" import { useMutation, useQueryClient } from "@tanstack/react-query"
import { qaPrompt } from "@/libs/playground.tsx"
export const PlaygroundEmpty = () => { export const PlaygroundEmpty = () => {
const { const { onSubmit } = useMessageOption()
onSubmit,
setMessages,
setHistory,
setHistoryId,
historyId,
clearChat,
setSelectedModel,
temporaryChat,
setSelectedSystemPrompt
} = useMessageOption()
const queryClient = useQueryClient() 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({ const { mutateAsync: sendMessage } = useMutation({
mutationFn: onSubmit, mutationFn: onSubmit,
onSuccess: () => { onSuccess: () => {
@ -77,32 +18,36 @@ export const PlaygroundEmpty = () => {
} }
}) })
function handleQuestion(message: string) { function handleQuestion(message: string) {
void sendMessage({message, image: ''}) void sendMessage({ message, image: "" })
} }
return ( return (
<div className="w-full p-4"> <div className="w-full p-4">
{/* 标题区域 */} {/* 标题区域 */}
<div className="mb-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> <p className="text-sm text-gray-500"></p>
</div> </div>
{/* 卡片网格布局 */} {/* 卡片网格布局 */}
<Row gutter={[16, 16]} className="w-full"> <Row gutter={[16, 16]} className="w-full">
{questions.map((item, index) => ( {qaPrompt.map((item, index) => (
<Col key={index} xs={24} sm={12} md={8}> <Col key={index} xs={24} sm={12} md={8}>
<Card <Card
hoverable hoverable
style={{backgroundColor: "#f3f4f6"}} style={{ backgroundColor: "#f3f4f6" }}
className="border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 cursor-pointer" 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="flex items-center">
<div className="text-blue-500 mr-2">{item.icon}</div> <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="font-medium text-sm text-gray-800">
{item.title}
</div>
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@ -357,6 +357,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
if (isListening) { if (isListening) {
stopSpeechRecognition() stopSpeechRecognition()
} else { } else {
console.log("开始语音识别,语言:", speechToTextLanguage);
resetTranscript() resetTranscript()
startListening({ startListening({
continuous: true, continuous: true,

View File

@ -172,9 +172,9 @@ export const Sidebar = ({
key={index} key={index}
className={` 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 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-[#615ced] hover:bg-[#f3f2ff] dark:hover:bg-[#2d2d2d] dark:border-gray-800 hover:text-[#000000d9] hover:bg-[#f3f2ff] dark:hover:bg-[#2d2d2d] dark:border-gray-800
hover:[&_.more-vertical]:text-[#615ced] hover:[&_.more-vertical]:text-[#000000d9]
${historyId === chat.id ? 'text-[#615ced] bg-[#f3f2ff] border-[#615ced]' : 'dark:text-gray-100 text-gray-800'} ${historyId === chat.id ? 'text-[#000000d9] bg-[#f3f2ff] border-[#000000d9]' : 'dark:text-gray-100 text-gray-800'}
`}> `}>
{chat?.message_source === "copilot" && ( {chat?.message_source === "copilot" && (
<Tooltip title={t("common:sidebarChat")} placement="top"> <Tooltip title={t("common:sidebarChat")} placement="top">
@ -271,7 +271,7 @@ export const Sidebar = ({
trigger={["click"]} trigger={["click"]}
placement="bottomRight"> placement="bottomRight">
<button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100"> <button className="text-gray-500 dark:text-gray-400 opacity-80 hover:opacity-100">
<MoreVertical className={`group-hover:text-[#615ced] w-4 h-4 more-vertical ${historyId === chat.id ? 'text-[#615ced]' : ''}`} /> <MoreVertical className={`group-hover:text-[#000000d9] w-4 h-4 more-vertical ${historyId === chat.id ? 'text-[#000000d9]' : ''}`} />
</button> </button>
</Dropdown> </Dropdown>
</div> </div>

51
src/libs/playground.tsx Normal file
View File

@ -0,0 +1,51 @@
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-full my-0" />,
},
{
title: "生成式AI在企业中有哪些具体应用场景",
icon: <img src={BulbSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "多模态学习技术的最新研究方向是什么?",
icon: <img src={EyeSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "当前AI芯片市场格局和未来三年发展趋势如何",
icon: <img src={ASvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "主流深度学习框架性能与易用性对比分析?",
icon: <img src={BSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "国内外AI伦理治理框架有哪些最佳实践",
icon: <img src={CSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "大规模知识图谱构建与应用最新进展?",
icon: <img src={DSvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "计算机视觉领域近期突破性技术有哪些?",
icon: <img src={ESvg} alt="Rocket" className="w-full my-0" />,
},
{
title: "量子计算对AI算法的影响与应用前景",
icon: <img src={FSvg} alt="Rocket" className="w-full my-0" />,
},
].map((item, index) => ({
...item,
id: index.toString(),
}))