refactor(layout): 重构布局组件并添加视频播放功能
-重写 Header 组件,使用新的 OptionLayoutContext 替代 HistoryContext - 新增 VideoPlayer 组件,用于播放视频 - 更新 Playground 组件,集成新的侧边栏和视频播放功能 - 重构 Layout 组件,支持新的选项布局 - 更新相关路由和导出导入逻辑,以支持上述更改
This commit is contained in:
@@ -2,6 +2,7 @@ import React from "react"
|
||||
|
||||
import { PlaygroundForm } from "./PlaygroundForm"
|
||||
import { PlaygroundChat } from "./PlaygroundChat"
|
||||
import { PlaygroundSidebar } from "./PlaygroundSidebar.tsx"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
||||
import { webUIResumeLastChat } from "@/services/app"
|
||||
|
||||
@@ -15,7 +16,6 @@ import { getLastUsedChatSystemPrompt } from "@/services/model-settings"
|
||||
import { useStoreChatModelSettings } from "@/store/model"
|
||||
import { useSmartScroll } from "@/hooks/useSmartScroll"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { PlaygroundHistory } from "@/components/Common/Playground/History.tsx"
|
||||
import { PlaygroundIod } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||
|
||||
export const Playground = () => {
|
||||
@@ -139,7 +139,7 @@ export const Playground = () => {
|
||||
className={`relative flex gap-3 h-full items-center ${
|
||||
dropState === "dragging" ? "bg-gray-100 dark:bg-gray-800" : ""
|
||||
} bg-white dark:bg-[#171717]`}>
|
||||
<PlaygroundHistory />
|
||||
<PlaygroundSidebar />
|
||||
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
||||
@@ -48,9 +48,6 @@ const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
const [detailMain, setDetailMain] = useState(<></>)
|
||||
|
||||
const currentIodMessage = useMemo<AllIodRegistryEntry | undefined>(() => {
|
||||
console.log('messages', messages)
|
||||
console.log("currentMessageId", currentMessageId)
|
||||
console.log("iodLoading", iodLoading)
|
||||
// loading 返回 undefined是为了避免,数据不足三个的情况
|
||||
if (iodLoading || !messages.length) {
|
||||
return undefined
|
||||
@@ -66,7 +63,6 @@ const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
const currentMessage = messages?.find(
|
||||
(message) => message.id === currentMessageId
|
||||
)
|
||||
console.log("currentMessage", currentMessage)
|
||||
return currentMessage?.iodSearch ? currentMessage.iodSources : undefined
|
||||
}, [currentMessageId, messages, iodLoading])
|
||||
|
||||
|
||||
277
src/components/Option/Playground/PlaygroundSidebar.tsx
Normal file
277
src/components/Option/Playground/PlaygroundSidebar.tsx
Normal file
@@ -0,0 +1,277 @@
|
||||
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
||||
import React, { useMemo } from "react"
|
||||
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
||||
import { useStoreChatModelSettings } from "@/store/model.tsx"
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Menu,
|
||||
MenuProps,
|
||||
Popover,
|
||||
Select,
|
||||
Tooltip
|
||||
} from "antd"
|
||||
import { PageAssitDatabase } from "@/db"
|
||||
import { EraserIcon, PanelLeftIcon } from "lucide-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { useOptionLayoutContext } 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"
|
||||
import logo from "@/assets/logo.png"
|
||||
|
||||
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 PlaygroundSidebar = () => {
|
||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||
|
||||
const { showOptionSidebar, setShowOptionSidebar, setShowVideo } = useOptionLayoutContext()
|
||||
|
||||
const {
|
||||
setMessages,
|
||||
setHistory,
|
||||
setHistoryId,
|
||||
historyId,
|
||||
clearChat,
|
||||
selectedModel,
|
||||
setSelectedModel,
|
||||
temporaryChat,
|
||||
setSelectedSystemPrompt,
|
||||
stopStreamingRequest
|
||||
} = useMessageOption()
|
||||
|
||||
const { t } = useTranslation(["option", "common", "settings"])
|
||||
|
||||
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: (
|
||||
<div className="flex items-center gap-2 truncate w-full">
|
||||
<p className="w-5 h-5 [&_.ant-avatar]:!w-full [&_.ant-avatar]:!h-full [&_.ant-avatar]:relative [&_.ant-avatar]:-top-3">
|
||||
{item.icon}
|
||||
</p>
|
||||
<span className="flex-1 truncate" title={item.title}>
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
}, [])
|
||||
|
||||
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 } = useQuery({
|
||||
queryKey: ["fetchModel"],
|
||||
queryFn: () => fetchChatModels({ returnEmpty: true }),
|
||||
refetchIntervalInBackground: false,
|
||||
placeholderData: (prev) => prev
|
||||
})
|
||||
|
||||
// 是否隐藏logo
|
||||
const hideLogo = useMemo(() => {
|
||||
return localStorage.getItem("hideLogo") === "true"
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`flex flex-col [&_.ant-card-body]:h-full w-[300px] overflow-hidden h-full pb-5 transition-all duration-300 ease-in-out backdrop-blur-lg !bg-[#f3f4f6]`}
|
||||
style={{ width: showOptionSidebar ? "300px" : "0" }}>
|
||||
{/*Header*/}
|
||||
<div className="flex flex-col overflow-y-hidden h-full">
|
||||
<div className="flex items-center justify-between transition-all duration-300 ease-in-out w-[250px]">
|
||||
<div className="flex items-center gap-2 cursor-pointer" onClick={() => setShowVideo(true)}>
|
||||
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
|
||||
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="text-gray-500 dark:text-gray-400"
|
||||
onClick={() => {
|
||||
setShowOptionSidebar(!showOptionSidebar)
|
||||
}}>
|
||||
<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">
|
||||
<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
|
||||
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 flex-1 pl-7">
|
||||
<Sidebar
|
||||
onClose={() => setShowOptionSidebar(true)}
|
||||
setMessages={setMessages}
|
||||
setHistory={setHistory}
|
||||
setHistoryId={setHistoryId}
|
||||
setSelectedModel={setSelectedModel}
|
||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||
clearChat={clearChat}
|
||||
historyId={historyId}
|
||||
setSystemPrompt={setSystemPrompt}
|
||||
temporaryChat={temporaryChat}
|
||||
stopStreamingRequest={stopStreamingRequest}
|
||||
history={history}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user