refactor(layout): 重构布局组件并添加视频播放功能
-重写 Header 组件,使用新的 OptionLayoutContext 替代 HistoryContext - 新增 VideoPlayer 组件,用于播放视频 - 更新 Playground 组件,集成新的侧边栏和视频播放功能 - 重构 Layout 组件,支持新的选项布局 - 更新相关路由和导出导入逻辑,以支持上述更改
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useForm } from "@mantine/form"
|
||||
import React from "react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||
import TextArea from "antd/es/input/TextArea"
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
@@ -14,6 +15,14 @@ export const EditMessageForm = (props: Props) => {
|
||||
const [isComposing, setIsComposing] = React.useState(false)
|
||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
||||
const { t } = useTranslation("common")
|
||||
const [value, setValue] = useState(props.value);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
setValue(props.value)
|
||||
},
|
||||
[props.value]
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
@@ -29,46 +38,27 @@ export const EditMessageForm = (props: Props) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((data) => {
|
||||
if (isComposing) return
|
||||
props.onClose()
|
||||
props.onSumbit(data.message, true)
|
||||
props.onSumbit(value, true)
|
||||
})}
|
||||
className="flex flex-col gap-2">
|
||||
<textarea
|
||||
{...form.getInputProps("message")}
|
||||
onCompositionStart={() => setIsComposing(true)}
|
||||
onCompositionEnd={() => setIsComposing(false)}
|
||||
className="flex flex-col gap-2 w-96 ml-auto">
|
||||
<TextArea
|
||||
required
|
||||
rows={1}
|
||||
rows={2}
|
||||
value={value}
|
||||
style={{ minHeight: "60px" }}
|
||||
tabIndex={0}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value)
|
||||
}}
|
||||
placeholder={t("editMessage.placeholder")}
|
||||
ref={textareaRef}
|
||||
className="w-full bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<div
|
||||
className={`w-full flex ${
|
||||
!props.isBot ? "justify-between" : "justify-end"
|
||||
!props.isBot ? "justify-end" : "justify-end"
|
||||
}`}>
|
||||
{!props.isBot && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
props.onSumbit(form.values.message, false)
|
||||
props.onClose()
|
||||
}}
|
||||
aria-label={t("save")}
|
||||
className="border border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
|
||||
{t("save")}
|
||||
</button>
|
||||
)}
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
aria-label={t("save")}
|
||||
className="bg-black px-2 py-1.5 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-900 text-sm">
|
||||
{props.isBot ? t("save") : t("saveAndSubmit")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={props.onClose}
|
||||
@@ -76,6 +66,12 @@ export const EditMessageForm = (props: Props) => {
|
||||
className="border dark:border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
|
||||
{t("cancel")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
aria-label={t("save")}
|
||||
className="bg-[#0057ff] px-2 py-1.5 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 text-sm">
|
||||
{props.isBot ? t("save") : t("saveAndSubmit")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>{" "}
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
||||
import React, { useContext, 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 { 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"
|
||||
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 PlaygroundHistory = () => {
|
||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||
|
||||
const { show, setShow } = useContext(HistoryContext)
|
||||
|
||||
const {
|
||||
setMessages,
|
||||
setHistory,
|
||||
setHistoryId,
|
||||
historyId,
|
||||
clearChat,
|
||||
selectedModel,
|
||||
setSelectedModel,
|
||||
temporaryChat,
|
||||
setSelectedSystemPrompt
|
||||
} = 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: show ? "300px" : "0" }}>
|
||||
{/*Header*/}
|
||||
<div className="flex flex-col overflow-y-hidden h-full">
|
||||
<div className="flex items-center justify-between transition-all duration-300 ease-in-out w-[250px]">
|
||||
{!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>
|
||||
|
||||
<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">
|
||||
<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={() => setShow(true)}
|
||||
setMessages={setMessages}
|
||||
setHistory={setHistory}
|
||||
setHistoryId={setHistoryId}
|
||||
setSelectedModel={setSelectedModel}
|
||||
setSelectedSystemPrompt={setSelectedSystemPrompt}
|
||||
clearChat={clearChat}
|
||||
historyId={historyId}
|
||||
setSystemPrompt={setSystemPrompt}
|
||||
temporaryChat={temporaryChat}
|
||||
history={history}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -275,7 +275,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
/>
|
||||
个{" "}
|
||||
</span>
|
||||
数据集
|
||||
数据集,引用 3个 数据集作为参考
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
@@ -314,7 +314,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
/>
|
||||
个{" "}
|
||||
</span>
|
||||
场景
|
||||
场景,引用 3个 场景作为参考
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
@@ -358,7 +358,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
||||
/>
|
||||
个{" "}
|
||||
</span>
|
||||
组织
|
||||
组织,引用 3个 组织作为参考
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
|
||||
@@ -82,7 +82,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div className={`flex flex-grow flex-col`}>
|
||||
<div className={`flex flex-grow flex-col w-full`}>
|
||||
{!editMode ? (
|
||||
props.isBot ? (
|
||||
<>
|
||||
@@ -239,7 +239,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
// : "flex"
|
||||
}`}>
|
||||
{props.isTTSEnabled && (
|
||||
<Tooltip title={t("tts")}>
|
||||
<Tooltip title={t("tts")} className="hidden">
|
||||
<button
|
||||
aria-label={t("tts")}
|
||||
onClick={() => {
|
||||
@@ -297,6 +297,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
|
||||
{props.generationInfo && (
|
||||
<Popover
|
||||
className="hidden"
|
||||
content={
|
||||
<GenerationInfo generationInfo={props.generationInfo} />
|
||||
}
|
||||
@@ -322,8 +323,8 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!props.hideEditAndRegenerate && (
|
||||
<Tooltip title={t("edit")}>
|
||||
{(!props.hideEditAndRegenerate && !props.isBot) && (
|
||||
<Tooltip title={t("edit")} className="hidden">
|
||||
<button
|
||||
onClick={() => setEditMode(true)}
|
||||
aria-label={t("edit")}
|
||||
@@ -333,7 +334,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
{
|
||||
<Tooltip title="收藏">
|
||||
<Tooltip title="收藏" className="hidden">
|
||||
<button
|
||||
aria-label="收藏"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
@@ -342,7 +343,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
<Tooltip title="发布语用">
|
||||
<Tooltip title="发布语用" className="hidden">
|
||||
<button
|
||||
aria-label="发布语用"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
@@ -351,7 +352,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
<Tooltip title="发布对话">
|
||||
<Tooltip title="发布对话" className="hidden">
|
||||
<button
|
||||
aria-label="发布对话"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
@@ -360,7 +361,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
<Tooltip title="点赞">
|
||||
<Tooltip title="点赞" className="hidden">
|
||||
<button
|
||||
aria-label="点赞"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
@@ -369,7 +370,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
<Tooltip title="点踩">
|
||||
<Tooltip title="点踩" className="hidden">
|
||||
<button
|
||||
aria-label="点踩"
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
|
||||
|
||||
Reference in New Issue
Block a user