refactor(layout): 重构布局组件并添加新功能
- 更新 Header 组件,增加项目标题和历史记录切换按钮 - 新增 DataNavigation 组件用于数据导航 - 添加 Playground 相关新组件,包括数据、场景、团队等信息展示 - 重构 Layout 组件,使用 Context 管理历史记录状态 - 更新 zh/option.json 文件,添加新的项目标题和对话相关翻译
This commit is contained in:
38
src/components/Common/DataNavigation.tsx
Normal file
38
src/components/Common/DataNavigation.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { Typography, Button } from "antd";
|
||||
import { AcademicCapIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
type Props = {
|
||||
Header: React.ReactNode;
|
||||
showButton?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const DataNavigation: React.FC<Props> = ({ Header, showButton = true, onClick }) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between bg-white dark:bg-gray-800 rounded-lg mb-3">
|
||||
{/* 左侧部分 */}
|
||||
<div className="flex items-center">
|
||||
<Title
|
||||
level={4}
|
||||
style={{ marginBottom: 0, color: "#1F2937", fontSize: "16px" }}>
|
||||
{Header}
|
||||
</Title>
|
||||
</div>
|
||||
|
||||
{/* 右侧部分 */}
|
||||
{showButton && (
|
||||
<Button
|
||||
color="default"
|
||||
variant="link"
|
||||
style={{ gap: 4 }}
|
||||
onClick={onClick}>
|
||||
<span className="text-sm">更多</span>
|
||||
<ChevronRightIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -12,7 +12,7 @@ import { preprocessLaTeX } from "@/utils/latex"
|
||||
|
||||
function Markdown({
|
||||
message,
|
||||
className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
|
||||
className = "prose-lg break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"
|
||||
}: {
|
||||
message: string
|
||||
className?: string
|
||||
|
||||
175
src/components/Common/Playground/Data.tsx
Normal file
175
src/components/Common/Playground/Data.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import React from "react"
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Drawer, List } from "antd"
|
||||
import { useCallback, useState } from "react"
|
||||
|
||||
export const PlaygroundData = () => {
|
||||
// 模拟数据
|
||||
const data: {
|
||||
title: string
|
||||
description: string
|
||||
time: string
|
||||
metadata?: string
|
||||
}[] = [
|
||||
{
|
||||
title: "2019-2024年黄海清浅海域中河湖代数生物物种数据集",
|
||||
description:
|
||||
"数字对象标识: CSTR:13452.11.01.11.2021.242 国家海洋科学数据中心",
|
||||
time: "包括2019年8月,2021年8月和2024年6月",
|
||||
metadata: "热 榜 第"
|
||||
},
|
||||
{
|
||||
title: "祁连山老虎沟大本营10米气象每日值数据集(V1.0)(2018-2023)",
|
||||
description:
|
||||
"中国科学院西北生态环境资源研究院,2021年8月3日发布,2021年8月3日20:48更新",
|
||||
time: "包括2019年8月,2021年8月和2024年6月",
|
||||
metadata: "热 榜 第"
|
||||
},
|
||||
{
|
||||
title: "李嘉图为研究老虎沟大本营2014-2018年...",
|
||||
description:
|
||||
"中国科学院西北生态环境资源研究院,2021年8月3日发布,2021年8月3日20:48更新",
|
||||
time: "包括2019年8月,2021年8月和2024年6月",
|
||||
metadata: "热 榜 第"
|
||||
},
|
||||
{
|
||||
title: "青海玉树B1区俄日矿勘探数据2017-2023",
|
||||
description:
|
||||
"数字中国集团,CSTR:3260.11.1528414774204895456,DT2023年地质勘探补充调查",
|
||||
time: "包括2019年8月,2021年8月和2024年6月",
|
||||
metadata: "热 榜 第"
|
||||
}
|
||||
]
|
||||
|
||||
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 [open, setOpen] = useState(false)
|
||||
|
||||
const showDrawer = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-y-hidden">
|
||||
{/* 数据导航 */}
|
||||
<DataNavigation
|
||||
Header={
|
||||
<div className="flex items-center text-[#1d4eD8] gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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
|
||||
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>
|
||||
相关数据
|
||||
</div>
|
||||
}
|
||||
onClick={showDrawer}
|
||||
/>
|
||||
|
||||
{/* 数据列表 */}
|
||||
<div className="space-y-1.5 flex-1 overflow-y-auto">
|
||||
{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="相关数据"
|
||||
closable={{ "aria-label": "Close Button" }}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
84
src/components/Common/Playground/History.tsx
Normal file
84
src/components/Common/Playground/History.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
||||
import React, { useContext, useState } from "react"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||
import { useStoreChatModelSettings } from "@/store/model.tsx"
|
||||
import { Card, Tooltip } from "antd"
|
||||
import { PageAssitDatabase } from "@/db"
|
||||
import { EraserIcon } from "lucide-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
|
||||
|
||||
export const PlaygroundHistory = () => {
|
||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||
|
||||
const { show, setShow } = useContext(HistoryContext)
|
||||
|
||||
const {
|
||||
setMessages,
|
||||
setHistory,
|
||||
setHistoryId,
|
||||
historyId,
|
||||
clearChat,
|
||||
setSelectedModel,
|
||||
temporaryChat,
|
||||
setSelectedSystemPrompt
|
||||
} = useMessageOption()
|
||||
|
||||
const { t } = useTranslation(["option", "common", "settings"])
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return (
|
||||
<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 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">
|
||||
{t("sidebarTitle")}
|
||||
<Tooltip
|
||||
title={t("settings:generalSettings.system.deleteChatHistory.label")}
|
||||
placement="right">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const confirm = window.confirm(
|
||||
t("settings:generalSettings.system.deleteChatHistory.confirm")
|
||||
)
|
||||
|
||||
if (confirm) {
|
||||
const db = new PageAssitDatabase()
|
||||
await db.deleteAllChatHistory()
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["fetchChatHistory"]
|
||||
})
|
||||
clearChat()
|
||||
}
|
||||
}}
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
|
||||
<EraserIcon className="size-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}>
|
||||
<div className="overflow-y-auto">
|
||||
<Sidebar
|
||||
onClose={() => setShow(true)}
|
||||
setMessages={setMessages}
|
||||
setHistory={setHistory}
|
||||
setHistoryId={setHistoryId}
|
||||
setSelectedModel={setSelectedModel}
|
||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||
clearChat={clearChat}
|
||||
historyId={historyId}
|
||||
setSystemPrompt={setSystemPrompt}
|
||||
temporaryChat={temporaryChat}
|
||||
history={history}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
95
src/components/Common/Playground/IodRelevant.tsx
Normal file
95
src/components/Common/Playground/IodRelevant.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
|
||||
const SuccessIcon = () => {
|
||||
return (<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-full h-full text-green-500">
|
||||
<path
|
||||
d="M9 12L11 14L15 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>)
|
||||
}
|
||||
|
||||
const LoadingIcon = () => {
|
||||
return (
|
||||
<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="18"
|
||||
height="18">
|
||||
<defs></defs>
|
||||
<g>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const PlaygroundIodRelevant: React.FC = () => {
|
||||
const data = [
|
||||
{
|
||||
title: "已在29个科学数据中心的50万个科学数据集中进行搜索",
|
||||
description: "已发现4个数据集",
|
||||
status: "success"
|
||||
},
|
||||
{
|
||||
title: "已在100万篇论文、2800个科创场景中进行搜索",
|
||||
description: "已发现4个数据集",
|
||||
status: "success"
|
||||
},
|
||||
{
|
||||
title: "正在1000位智库专家、12万个创新机构中进行搜索",
|
||||
status: "loading"
|
||||
},
|
||||
]
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
data.push({
|
||||
title: "已在29个科学数据中心的50万个科学数据集中进行搜索" + i,
|
||||
description: "已发现4个数据集",
|
||||
status: "success"
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
数联网搜索相关内
|
||||
</h2>
|
||||
<span className="text-sm text-blue-600 font-medium">{data.length}个结果</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<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>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-gray-700">
|
||||
{item.title}
|
||||
</p>
|
||||
{item.description && <p className="text-xs text-gray-500 mt-1">{item.description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export const PlaygroundMessage = (props: Props) => {
|
||||
const { t } = useTranslation("common")
|
||||
const { cancel, isSpeaking, speak } = useTTS()
|
||||
return (
|
||||
<div className="group relative flex w-full max-w-3xl flex-col items-end justify-center pb-2 md:px-4 lg:w-4/5 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="flex flex-row gap-4 md:gap-6 my-2 m-auto w-full">
|
||||
<div className="w-8 flex flex-col relative items-end">
|
||||
@@ -138,7 +138,7 @@ export const PlaygroundMessage = (props: Props) => {
|
||||
</>
|
||||
) : (
|
||||
<p
|
||||
className={`prose 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 &&
|
||||
"italic text-gray-500 dark:text-gray-400 text-sm"
|
||||
}`}>
|
||||
|
||||
142
src/components/Common/Playground/Scene.tsx
Normal file
142
src/components/Common/Playground/Scene.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React, { useState } from "react"
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Drawer, List } from "antd"
|
||||
|
||||
export const PlaygroundScene = () => {
|
||||
// 模拟数据
|
||||
const data = [
|
||||
{
|
||||
title: "绿色化工工艺项目",
|
||||
description:
|
||||
"基于生物基原料,采用repeal2.0可降解材料技术,开发新型环保材料。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "智能农业解决方案",
|
||||
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "新能源汽车电池技术",
|
||||
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "碳捕集与封存技术",
|
||||
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
}
|
||||
]
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
data.push({
|
||||
title: "开发新型电池材料",
|
||||
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
})
|
||||
}
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const showDrawer = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-hidden flex flex-col">
|
||||
{/* 数据导航 */}
|
||||
<DataNavigation
|
||||
Header={
|
||||
<div className="flex items-center text-[#15803d] gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
|
||||
viewBox="0 0 32 32"
|
||||
width="20"
|
||||
height="20">
|
||||
<defs></defs>
|
||||
<g>
|
||||
<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>
|
||||
相关场景
|
||||
</div>
|
||||
}
|
||||
onClick={showDrawer}
|
||||
/>
|
||||
|
||||
{/* 场景列表 */}
|
||||
<div className="space-y-1.5 flex-1 overflow-y-auto">
|
||||
{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="相关场景"
|
||||
closable={{ "aria-label": "Close Button" }}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
136
src/components/Common/Playground/Team.tsx
Normal file
136
src/components/Common/Playground/Team.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React, { useState } from "react"
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Drawer, List } from "antd"
|
||||
|
||||
export const PlaygroundTeam = () => {
|
||||
// 模拟数据
|
||||
const data = [
|
||||
{
|
||||
title: "绿色化工工艺项目",
|
||||
description:
|
||||
"基于生物基原料,采用repeal2.0可降解材料技术,开发新型环保材料。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "智能农业解决方案",
|
||||
description: "利用物联网技术,实现精准农业管理,提高农作物产量。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "新能源汽车电池技术",
|
||||
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
},
|
||||
{
|
||||
title: "碳捕集与封存技术",
|
||||
description: "开发高效的碳捕集技术,减少工业排放,助力碳中和目标。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
}
|
||||
]
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
data.push({
|
||||
title: "开发新型电池材料",
|
||||
description: "研发高能量密度、长寿命的新型电池材料,推动电动汽车发展。",
|
||||
demander: "奥赛康药业 供方:美国Propella公司"
|
||||
})
|
||||
}
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const showDrawer = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-hidden flex flex-col">
|
||||
{/* 数据导航 */}
|
||||
<DataNavigation
|
||||
Header={
|
||||
<div className="flex items-center text-[#15803d] gap-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="styles__StyledSVGIconPathComponent-sc-i3aj97-0 bxMexi svg-icon-path-icon"
|
||||
viewBox="0 0 32 32"
|
||||
width="20"
|
||||
height="20">
|
||||
<defs></defs>
|
||||
<g>
|
||||
<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>
|
||||
相关团队
|
||||
</div>
|
||||
}
|
||||
onClick={showDrawer}
|
||||
/>
|
||||
|
||||
{/* 场景列表 */}
|
||||
<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="相关团队"
|
||||
closable={{ "aria-label": "Close Button" }}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
46
src/components/Common/Playground/TokenStatistics.tsx
Normal file
46
src/components/Common/Playground/TokenStatistics.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { DataNavigation } from "@/components/Common/DataNavigation.tsx"
|
||||
import { Card, Descriptions, DescriptionsProps, Drawer, List, Spin } from "antd"
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||
import { useStoreMessageOption } from "@/store/option.tsx"
|
||||
|
||||
export const PlaygroundTokenStatistics = () => {
|
||||
const { currentMeteringEntry } = useStoreMessageOption()
|
||||
|
||||
const items = useMemo<DescriptionsProps["items"]>(() => {
|
||||
const { data } = currentMeteringEntry
|
||||
return [
|
||||
// {
|
||||
// key: "relatedDataCount",
|
||||
// label: "关联数据个数",
|
||||
// children: data.relatedDataCount
|
||||
// },
|
||||
{
|
||||
key: "iodTokenCount",
|
||||
label: "数联网引用token总数",
|
||||
children: data.iodTokenCount ?? 0
|
||||
},
|
||||
{
|
||||
key: "modelInputTokenCount",
|
||||
label: "大模型输入token数量",
|
||||
children: data.modelInputTokenCount ?? 0
|
||||
},
|
||||
{
|
||||
key: "modelOutputTokenCount",
|
||||
label: "大模型输出token数量",
|
||||
children: data.modelOutputTokenCount ?? 0
|
||||
}
|
||||
]
|
||||
}, [currentMeteringEntry])
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={{ marginBottom: "1rem" }}
|
||||
className="h-full"
|
||||
title={<DataNavigation title="Token统计" showButton={false} />}>
|
||||
<Spin spinning={currentMeteringEntry.loading}>
|
||||
<Descriptions layout="horizontal" items={items} column={2} />
|
||||
</Spin>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -24,18 +24,24 @@ 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 = {
|
||||
setSidebarOpen: (open: boolean) => void
|
||||
sidebarOpen: boolean
|
||||
setSidebarOpen: () => void
|
||||
setOpenModelSettings: (open: boolean) => void
|
||||
}
|
||||
|
||||
export const Header: React.FC<Props> = ({
|
||||
setOpenModelSettings,
|
||||
setSidebarOpen
|
||||
setSidebarOpen,
|
||||
sidebarOpen
|
||||
}) => {
|
||||
const { t, i18n } = useTranslation(["option", "common"])
|
||||
const isRTL = i18n?.dir() === "rtl"
|
||||
|
||||
|
||||
|
||||
const [shareModeEnabled] = useStorage("shareMode", false)
|
||||
const [hideCurrentChatModelSettings] = useStorage(
|
||||
"hideCurrentChatModelSettings",
|
||||
@@ -109,10 +115,16 @@ export const Header: React.FC<Props> = ({
|
||||
</NavLink>
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="text-gray-500 dark:text-gray-400"
|
||||
onClick={() => setSidebarOpen(true)}>
|
||||
onClick={() => setSidebarOpen()}>
|
||||
<PanelLeftIcon className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,103 +1,50 @@
|
||||
import React, { useState } from "react"
|
||||
|
||||
import { Sidebar } from "../Option/Sidebar"
|
||||
import { Drawer, Tooltip } from "antd"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import React, { useCallback, useEffect, useState } from "react"
|
||||
|
||||
import { CurrentChatModelSettings } from "../Common/Settings/CurrentChatModelSettings"
|
||||
import { Header } from "./Header"
|
||||
import { EraserIcon } from "lucide-react"
|
||||
import { PageAssitDatabase } from "@/db"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { useStoreChatModelSettings } from "@/store/model"
|
||||
|
||||
interface History {
|
||||
show: boolean
|
||||
setShow: (show: boolean) => void
|
||||
}
|
||||
|
||||
export const HistoryContext = React.createContext<History>({
|
||||
show: true,
|
||||
setShow: () => {}
|
||||
})
|
||||
|
||||
export default function OptionLayout({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const { t } = useTranslation(["option", "common", "settings"])
|
||||
const [showHistory, setShowHistory] = useState(true)
|
||||
const [openModelSettings, setOpenModelSettings] = useState(false)
|
||||
const {
|
||||
setMessages,
|
||||
setHistory,
|
||||
setHistoryId,
|
||||
historyId,
|
||||
clearChat,
|
||||
setSelectedModel,
|
||||
temporaryChat,
|
||||
setSelectedSystemPrompt
|
||||
} = useMessageOption()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||
const historyContextValue = {
|
||||
show: showHistory,
|
||||
setShow: setShowHistory
|
||||
}
|
||||
|
||||
const useToggle = useCallback(() => {
|
||||
setShowHistory(!showHistory)
|
||||
}, [showHistory])
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full">
|
||||
<main className="relative h-dvh w-full">
|
||||
<div className="relative z-10 w-full">
|
||||
<Header
|
||||
setSidebarOpen={setSidebarOpen}
|
||||
sidebarOpen={showHistory}
|
||||
setSidebarOpen={useToggle}
|
||||
setOpenModelSettings={setOpenModelSettings}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="relative flex h-full flex-col items-center"> */}
|
||||
{children}
|
||||
<HistoryContext.Provider value={historyContextValue}>
|
||||
{children}
|
||||
</HistoryContext.Provider>
|
||||
{/* </div> */}
|
||||
<Drawer
|
||||
title={
|
||||
<div className="flex items-center justify-between">
|
||||
{t("sidebarTitle")}
|
||||
|
||||
<Tooltip
|
||||
title={t(
|
||||
"settings:generalSettings.system.deleteChatHistory.label"
|
||||
)}
|
||||
placement="right">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const confirm = window.confirm(
|
||||
t(
|
||||
"settings:generalSettings.system.deleteChatHistory.confirm"
|
||||
)
|
||||
)
|
||||
|
||||
if (confirm) {
|
||||
const db = new PageAssitDatabase()
|
||||
await db.deleteAllChatHistory()
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["fetchChatHistory"]
|
||||
})
|
||||
clearChat()
|
||||
}
|
||||
}}
|
||||
className="text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-100">
|
||||
<EraserIcon className="size-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
placement="left"
|
||||
closeIcon={null}
|
||||
onClose={() => setSidebarOpen(false)}
|
||||
open={sidebarOpen}>
|
||||
<Sidebar
|
||||
onClose={() => setSidebarOpen(false)}
|
||||
setMessages={setMessages}
|
||||
setHistory={setHistory}
|
||||
setHistoryId={setHistoryId}
|
||||
setSelectedModel={setSelectedModel}
|
||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||
clearChat={clearChat}
|
||||
historyId={historyId}
|
||||
setSystemPrompt={setSystemPrompt}
|
||||
temporaryChat={temporaryChat}
|
||||
history={history}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
<CurrentChatModelSettings
|
||||
open={openModelSettings}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import React from "react"
|
||||
|
||||
import { Card } from "antd"
|
||||
|
||||
import { PlaygroundForm } from "./PlaygroundForm"
|
||||
import { PlaygroundChat } from "./PlaygroundChat"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
||||
import { webUIResumeLastChat } from "@/services/app"
|
||||
import { PlaygroundData } from '@/components/Common/Playground/Data.tsx'
|
||||
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
|
||||
|
||||
import {
|
||||
formatToChatHistory,
|
||||
formatToMessage,
|
||||
@@ -13,6 +19,11 @@ import { getLastUsedChatSystemPrompt } from "@/services/model-settings"
|
||||
import { useStoreChatModelSettings } from "@/store/model"
|
||||
import { useSmartScroll } from "@/hooks/useSmartScroll"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
|
||||
import { 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)
|
||||
@@ -132,26 +143,43 @@ export const Playground = () => {
|
||||
return (
|
||||
<div
|
||||
ref={drop}
|
||||
className={`relative flex h-full flex-col items-center ${
|
||||
className={`relative flex gap-3 h-full items-center ${
|
||||
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
|
||||
} bg-white dark:bg-[#171717]`}>
|
||||
<div
|
||||
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">
|
||||
<PlaygroundChat />
|
||||
<PlaygroundHistory />
|
||||
<div className="relative h-full flex-1 prose-lg flex justify-center [&>*]:max-w-[848px]">
|
||||
<div
|
||||
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">
|
||||
<PlaygroundChat />
|
||||
</div>
|
||||
<div className="absolute bottom-0 w-full">
|
||||
{!isAtBottom && (
|
||||
<div className="absolute bottom-36 z-20 left-0 right-0 flex justify-center">
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto">
|
||||
<ChevronDown className="size-4 text-gray-600 dark:text-gray-300" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<PlaygroundForm dropedFile={dropedFile} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 w-full">
|
||||
{!isAtBottom && (
|
||||
<div className="fixed bottom-36 z-20 left-0 right-0 flex justify-center">
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto">
|
||||
<ChevronDown className="size-4 text-gray-600 dark:text-gray-300" />
|
||||
</button>
|
||||
{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>
|
||||
)}
|
||||
<PlaygroundForm dropedFile={dropedFile} />
|
||||
</div>
|
||||
<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 p-3 pb-0">
|
||||
<PlaygroundTeam />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PlaygroundChat = () => {
|
||||
<>
|
||||
<div className="relative flex w-full flex-col items-center pt-16 pb-4">
|
||||
{messages.length === 0 && (
|
||||
<div className="mt-32 w-full">
|
||||
<div className="mt-3 w-full">
|
||||
<PlaygroundEmpty />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,130 +1,113 @@
|
||||
import { cleanUrl } from "@/libs/clean-url"
|
||||
import { useStorage } from "@plasmohq/storage/hook"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { RotateCcw } from "lucide-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import {
|
||||
getOllamaURL,
|
||||
isOllamaRunning,
|
||||
setOllamaURL as saveOllamaURL
|
||||
} from "~/services/ollama"
|
||||
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"
|
||||
|
||||
|
||||
export const PlaygroundEmpty = () => {
|
||||
const [ollamaURL, setOllamaURL] = useState<string>("")
|
||||
const { t } = useTranslation(["playground", "common"])
|
||||
|
||||
const [checkOllamaStatus] = useStorage("checkOllamaStatus", true)
|
||||
|
||||
const {
|
||||
data: ollamaInfo,
|
||||
status: ollamaStatus,
|
||||
refetch,
|
||||
isRefetching
|
||||
} = useQuery({
|
||||
queryKey: ["ollamaStatus"],
|
||||
queryFn: async () => {
|
||||
const ollamaURL = await getOllamaURL()
|
||||
const isOk = await isOllamaRunning()
|
||||
onSubmit,
|
||||
setMessages,
|
||||
setHistory,
|
||||
setHistoryId,
|
||||
historyId,
|
||||
clearChat,
|
||||
setSelectedModel,
|
||||
temporaryChat,
|
||||
setSelectedSystemPrompt
|
||||
} = useMessageOption()
|
||||
|
||||
if (ollamaURL) {
|
||||
saveOllamaURL(ollamaURL)
|
||||
}
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return {
|
||||
isOk,
|
||||
ollamaURL
|
||||
}
|
||||
|
||||
const questions = [
|
||||
{
|
||||
title: "最近一年大型语言模型的技术进展有哪些?",
|
||||
icon: <img src={RocketSvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
enabled: checkOllamaStatus
|
||||
{
|
||||
title: "生成式AI在企业中有哪些具体应用场景?",
|
||||
icon: <img src={BulbSvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
{
|
||||
title: "多模态学习技术的最新研究方向是什么?",
|
||||
icon: <img src={EyeSvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
{
|
||||
title: "当前AI芯片市场格局和未来三年发展趋势如何?",
|
||||
icon: <img src={ASvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
{
|
||||
title: "主流深度学习框架性能与易用性对比分析?",
|
||||
icon: <img src={BSvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
{
|
||||
title: "国内外AI伦理治理框架有哪些最佳实践?",
|
||||
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: "量子计算对AI算法的影响与应用前景?",
|
||||
icon: <img src={FSvg} alt="Rocket" className="w-10 my-0" />,
|
||||
},
|
||||
]
|
||||
|
||||
const { mutateAsync: sendMessage } = useMutation({
|
||||
mutationFn: onSubmit,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["fetchChatHistory"]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (ollamaInfo?.ollamaURL) {
|
||||
setOllamaURL(ollamaInfo.ollamaURL)
|
||||
}
|
||||
}, [ollamaInfo])
|
||||
|
||||
|
||||
if (!checkOllamaStatus) {
|
||||
return (
|
||||
<div className="mx-auto sm:max-w-xl px-4 mt-10">
|
||||
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
|
||||
<h1 className="text-sm font-medium text-center text-gray-500 dark:text-gray-400 flex gap-3 items-center justify-center">
|
||||
<span >👋</span>
|
||||
<span className="text-gray-700 dark:text-gray-300">
|
||||
{t("welcome")}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
function handleQuestion(message: string) {
|
||||
void sendMessage({message, image: ''})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto sm:max-w-xl px-4 mt-10">
|
||||
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-gray-50 dark:bg-[#262626] dark:border-gray-600">
|
||||
{(ollamaStatus === "pending" || isRefetching) && (
|
||||
<div className="inline-flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>
|
||||
<p className="dark:text-gray-400 text-gray-900">
|
||||
{t("ollamaState.searching")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!isRefetching && ollamaStatus === "success" ? (
|
||||
ollamaInfo.isOk ? (
|
||||
<div className="inline-flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<p className="dark:text-gray-400 text-gray-900">
|
||||
{t("ollamaState.running")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-2 justify-center items-center">
|
||||
<div className="inline-flex space-x-2">
|
||||
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
|
||||
<p className="dark:text-gray-400 text-gray-900">
|
||||
{t("ollamaState.notRunning")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
className="bg-gray-100 dark:bg-[#262626] dark:text-gray-100 rounded-md px-4 py-2 mt-2 w-full"
|
||||
type="url"
|
||||
value={ollamaURL}
|
||||
onChange={(e) => setOllamaURL(e.target.value)}
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
saveOllamaURL(ollamaURL)
|
||||
refetch()
|
||||
}}
|
||||
className="inline-flex mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50 ">
|
||||
<RotateCcw className="h-4 w-4 mr-3" />
|
||||
{t("common:retry")}
|
||||
</button>
|
||||
|
||||
{ollamaURL &&
|
||||
cleanUrl(ollamaURL) !== "http://127.0.0.1:11434" && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4 text-center">
|
||||
<Trans
|
||||
i18nKey="playground:ollamaState.connectionError"
|
||||
components={{
|
||||
anchor: (
|
||||
<a
|
||||
href="https://github.com/n4ze3m/page-assist/blob/main/docs/connection-issue.md"
|
||||
target="__blank"
|
||||
className="text-blue-600 dark:text-blue-400"></a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
<div className="w-full p-4">
|
||||
{/* 标题区域 */}
|
||||
<div className="mb-4">
|
||||
<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">
|
||||
{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)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<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>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,11 +207,11 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center p-2 pt-1 pb-4">
|
||||
<div className="flex w-full flex-col items-center p-2 px-5 pt-1 pb-4">
|
||||
<div className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base">
|
||||
<div className="relative flex w-full flex-row justify-center gap-2 lg:w-4/5">
|
||||
<div className="relative flex w-full flex-row justify-center gap-2 lg:w-5/5">
|
||||
<div
|
||||
className={` bg-neutral-50 dark:bg-[#262626] relative w-full max-w-[48rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl dark:border-gray-600
|
||||
className={` bg-neutral-50 dark:bg-[#262626] relative w-full max-w-[65rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl dark:border-gray-600
|
||||
${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""}
|
||||
`}>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user