Compare commits
No commits in common. "c937694d8b999c8f91d4de3c629bfc55f581d3fa" and "121dfabbd1f057637785495ce4ddf92dd114bcaf" have entirely different histories.
c937694d8b
...
121dfabbd1
Binary file not shown.
@ -1,8 +1,7 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import React, { useEffect, useState } from "react"
|
import React from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
import TextArea from "antd/es/input/TextArea"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string
|
value: string
|
||||||
@ -15,14 +14,6 @@ export const EditMessageForm = (props: Props) => {
|
|||||||
const [isComposing, setIsComposing] = React.useState(false)
|
const [isComposing, setIsComposing] = React.useState(false)
|
||||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
||||||
const { t } = useTranslation("common")
|
const { t } = useTranslation("common")
|
||||||
const [value, setValue] = useState(props.value);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
setValue(props.value)
|
|
||||||
},
|
|
||||||
[props.value]
|
|
||||||
);
|
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@ -38,27 +29,46 @@ export const EditMessageForm = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((data) => {
|
onSubmit={form.onSubmit((data) => {
|
||||||
|
if (isComposing) return
|
||||||
props.onClose()
|
props.onClose()
|
||||||
props.onSumbit(value, true)
|
props.onSumbit(data.message, true)
|
||||||
})}
|
})}
|
||||||
className="flex flex-col gap-2 w-96 ml-auto">
|
className="flex flex-col gap-2">
|
||||||
<TextArea
|
<textarea
|
||||||
|
{...form.getInputProps("message")}
|
||||||
|
onCompositionStart={() => setIsComposing(true)}
|
||||||
|
onCompositionEnd={() => setIsComposing(false)}
|
||||||
required
|
required
|
||||||
rows={2}
|
rows={1}
|
||||||
value={value}
|
|
||||||
style={{ minHeight: "60px" }}
|
style={{ minHeight: "60px" }}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => {
|
|
||||||
setValue(e.target.value)
|
|
||||||
}}
|
|
||||||
placeholder={t("editMessage.placeholder")}
|
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="flex flex-wrap gap-2 mt-2">
|
||||||
<div
|
<div
|
||||||
className={`w-full flex ${
|
className={`w-full flex ${
|
||||||
!props.isBot ? "justify-end" : "justify-end"
|
!props.isBot ? "justify-between" : "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">
|
<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
|
<button
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
@ -66,12 +76,6 @@ 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">
|
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")}
|
{t("cancel")}
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
import { Sidebar } from "@/components/Option/Sidebar.tsx"
|
||||||
import React, { useMemo } from "react"
|
import React, { useContext, useMemo } 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 {
|
import {
|
||||||
@ -16,7 +16,7 @@ import { PageAssitDatabase } from "@/db"
|
|||||||
import { EraserIcon, PanelLeftIcon } from "lucide-react"
|
import { EraserIcon, PanelLeftIcon } from "lucide-react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
|
||||||
import { PlusOutlined, RightOutlined } from "@ant-design/icons"
|
import { PlusOutlined, RightOutlined } from "@ant-design/icons"
|
||||||
import { qaPrompt } from "@/libs/playground.tsx"
|
import { qaPrompt } from "@/libs/playground.tsx"
|
||||||
import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx"
|
import { ProviderIcons } from "@/components/Common/ProviderIcon.tsx"
|
||||||
@ -41,10 +41,10 @@ const ModelIcon = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaygroundSidebar = () => {
|
export const PlaygroundHistory = () => {
|
||||||
const { setSystemPrompt } = useStoreChatModelSettings()
|
const { setSystemPrompt } = useStoreChatModelSettings()
|
||||||
|
|
||||||
const { showOptionSidebar, setShowOptionSidebar, setShowVideo } = useOptionLayoutContext()
|
const { show, setShow } = useContext(HistoryContext)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setMessages,
|
setMessages,
|
||||||
@ -55,8 +55,7 @@ export const PlaygroundSidebar = () => {
|
|||||||
selectedModel,
|
selectedModel,
|
||||||
setSelectedModel,
|
setSelectedModel,
|
||||||
temporaryChat,
|
temporaryChat,
|
||||||
setSelectedSystemPrompt,
|
setSelectedSystemPrompt
|
||||||
stopStreamingRequest
|
|
||||||
} = useMessageOption()
|
} = useMessageOption()
|
||||||
|
|
||||||
const { t } = useTranslation(["option", "common", "settings"])
|
const { t } = useTranslation(["option", "common", "settings"])
|
||||||
@ -78,9 +77,7 @@ export const PlaygroundSidebar = () => {
|
|||||||
<p className="w-5 h-5 [&_.ant-avatar]:!w-full [&_.ant-avatar]:!h-full [&_.ant-avatar]:relative [&_.ant-avatar]:-top-3">
|
<p className="w-5 h-5 [&_.ant-avatar]:!w-full [&_.ant-avatar]:!h-full [&_.ant-avatar]:relative [&_.ant-avatar]:-top-3">
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</p>
|
</p>
|
||||||
<span className="flex-1 truncate" title={item.title}>
|
<span className="flex-1 truncate" title={item.title}>{item.title}</span>
|
||||||
{item.title}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -121,21 +118,19 @@ export const PlaygroundSidebar = () => {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<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]`}
|
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" }}>
|
style={{ width: show ? "300px" : "0" }}>
|
||||||
{/*Header*/}
|
{/*Header*/}
|
||||||
<div className="flex flex-col overflow-y-hidden h-full">
|
<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 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" />}
|
{!hideLogo && <img src={logo} alt="logo" className="w-8" />}
|
||||||
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
|
<h2 className="text-xl font-bold text-zinc-700 dark:text-zinc-300 mr-3">
|
||||||
<span className="text-[#d30100]">数联网</span>科创智能体
|
<span className="text-[#d30100]">数联网</span>科创智能体
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 dark:text-gray-400"
|
className="text-gray-500 dark:text-gray-400"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowOptionSidebar(!showOptionSidebar)
|
setShow(!show)
|
||||||
}}>
|
}}>
|
||||||
<PanelLeftIcon className="w-6 h-6" />
|
<PanelLeftIcon className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
@ -257,7 +252,7 @@ export const PlaygroundSidebar = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="overflow-y-auto flex-1 pl-7">
|
<div className="overflow-y-auto flex-1 pl-7">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
onClose={() => setShowOptionSidebar(true)}
|
onClose={() => setShow(true)}
|
||||||
setMessages={setMessages}
|
setMessages={setMessages}
|
||||||
setHistory={setHistory}
|
setHistory={setHistory}
|
||||||
setHistoryId={setHistoryId}
|
setHistoryId={setHistoryId}
|
||||||
@ -267,7 +262,6 @@ export const PlaygroundSidebar = () => {
|
|||||||
historyId={historyId}
|
historyId={historyId}
|
||||||
setSystemPrompt={setSystemPrompt}
|
setSystemPrompt={setSystemPrompt}
|
||||||
temporaryChat={temporaryChat}
|
temporaryChat={temporaryChat}
|
||||||
stopStreamingRequest={stopStreamingRequest}
|
|
||||||
history={history}
|
history={history}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
@ -275,7 +275,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
|||||||
/>
|
/>
|
||||||
个{" "}
|
个{" "}
|
||||||
</span>
|
</span>
|
||||||
数据集,引用 3个 数据集作为参考
|
数据集
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
@ -314,7 +314,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
|||||||
/>
|
/>
|
||||||
个{" "}
|
个{" "}
|
||||||
</span>
|
</span>
|
||||||
场景,引用 3个 场景作为参考
|
场景
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
@ -358,7 +358,7 @@ export const PlaygroundIodRelevant: React.FC<Props> = ({ className }) => {
|
|||||||
/>
|
/>
|
||||||
个{" "}
|
个{" "}
|
||||||
</span>
|
</span>
|
||||||
组织,引用 3个 组织作为参考
|
组织
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
@ -82,7 +82,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex flex-grow flex-col w-full`}>
|
<div className={`flex flex-grow flex-col`}>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
props.isBot ? (
|
props.isBot ? (
|
||||||
<>
|
<>
|
||||||
@ -239,7 +239,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
// : "flex"
|
// : "flex"
|
||||||
}`}>
|
}`}>
|
||||||
{props.isTTSEnabled && (
|
{props.isTTSEnabled && (
|
||||||
<Tooltip title={t("tts")} className="hidden">
|
<Tooltip title={t("tts")}>
|
||||||
<button
|
<button
|
||||||
aria-label={t("tts")}
|
aria-label={t("tts")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -297,7 +297,6 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
{props.generationInfo && (
|
{props.generationInfo && (
|
||||||
<Popover
|
<Popover
|
||||||
className="hidden"
|
|
||||||
content={
|
content={
|
||||||
<GenerationInfo generationInfo={props.generationInfo} />
|
<GenerationInfo generationInfo={props.generationInfo} />
|
||||||
}
|
}
|
||||||
@ -323,8 +322,8 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(!props.hideEditAndRegenerate && !props.isBot) && (
|
{!props.hideEditAndRegenerate && (
|
||||||
<Tooltip title={t("edit")} className="hidden">
|
<Tooltip title={t("edit")}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditMode(true)}
|
onClick={() => setEditMode(true)}
|
||||||
aria-label={t("edit")}
|
aria-label={t("edit")}
|
||||||
@ -334,7 +333,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
<Tooltip title="收藏" className="hidden">
|
<Tooltip title="收藏">
|
||||||
<button
|
<button
|
||||||
aria-label="收藏"
|
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">
|
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">
|
||||||
@ -343,7 +342,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
<Tooltip title="发布语用" className="hidden">
|
<Tooltip title="发布语用">
|
||||||
<button
|
<button
|
||||||
aria-label="发布语用"
|
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">
|
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">
|
||||||
@ -352,7 +351,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
<Tooltip title="发布对话" className="hidden">
|
<Tooltip title="发布对话">
|
||||||
<button
|
<button
|
||||||
aria-label="发布对话"
|
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">
|
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">
|
||||||
@ -361,7 +360,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
<Tooltip title="点赞" className="hidden">
|
<Tooltip title="点赞">
|
||||||
<button
|
<button
|
||||||
aria-label="点赞"
|
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">
|
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">
|
||||||
@ -370,7 +369,7 @@ export const PlaygroundMessage: React.FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
<Tooltip title="点踩" className="hidden">
|
<Tooltip title="点踩">
|
||||||
<button
|
<button
|
||||||
aria-label="点踩"
|
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">
|
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">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo, useState } from "react"
|
import React, { useContext, useMemo, useState } from "react"
|
||||||
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
import { HistoryContext } from "@/components/Layouts/Layout.tsx"
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
import { PanelLeftIcon } from "lucide-react"
|
||||||
import { Button, Tooltip } from "antd"
|
import { Button, Tooltip } from "antd"
|
||||||
import { PlusOutlined } from "@ant-design/icons"
|
import { PlusOutlined } from "@ant-design/icons"
|
||||||
@ -13,20 +13,22 @@ import { NotCollectIcon } from "@/components/Icons/NotCollect.tsx"
|
|||||||
import { CollectIcon } from "@/components/Icons/Collect.tsx"
|
import { CollectIcon } from "@/components/Icons/Collect.tsx"
|
||||||
import { SettingIcon } from "@/components/Icons/Setting.tsx"
|
import { SettingIcon } from "@/components/Icons/Setting.tsx"
|
||||||
|
|
||||||
type Props = {}
|
type Props = {
|
||||||
|
setOpenModelSettings: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
export const Header: React.FC<Props> = ({}) => {
|
export const Header: React.FC<Props> = ({ setOpenModelSettings }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const { showOptionSidebar, setShowOptionSidebar } = useOptionLayoutContext()
|
const { show, setShow } = useContext(HistoryContext)
|
||||||
|
|
||||||
const showLeft = useMemo<boolean>(() => {
|
const showLeft = useMemo<boolean>(() => {
|
||||||
console.log(location.pathname)
|
console.log(location.pathname)
|
||||||
if (location.pathname.includes("/settings")) {
|
if (location.pathname.includes("/settings")) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return showOptionSidebar
|
return show
|
||||||
}, [location.pathname, showOptionSidebar])
|
}, [location.pathname, show])
|
||||||
|
|
||||||
const { t } = useTranslation(["option", "common", "settings"])
|
const { t } = useTranslation(["option", "common", "settings"])
|
||||||
|
|
||||||
@ -40,14 +42,14 @@ export const Header: React.FC<Props> = ({}) => {
|
|||||||
const [collect, setCollect] = useState<boolean>(false)
|
const [collect, setCollect] = useState<boolean>(false)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${showOptionSidebar && !location.pathname.includes("/settings") ? "left-[300px]" : ""}`}>
|
className={`h-[60px] absolute inset-0 pl-5 z-10 flex items-center transition-all duration-300 ease-in-out ${show && !location.pathname.includes("/settings") ? "left-[300px]" : ""}`}>
|
||||||
{/*控制侧边栏显示隐藏与新建对话*/}
|
{/*控制侧边栏显示隐藏与新建对话*/}
|
||||||
{!showLeft && (
|
{!showLeft && (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 dark:text-gray-400"
|
className="text-gray-500 dark:text-gray-400"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowOptionSidebar(!showOptionSidebar)
|
setShow(!show)
|
||||||
}}>
|
}}>
|
||||||
<PanelLeftIcon className="w-6 h-6" />
|
<PanelLeftIcon className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
@ -92,7 +94,7 @@ export const Header: React.FC<Props> = ({}) => {
|
|||||||
w-[600px] h-[60px] dark:bg-black
|
w-[600px] h-[60px] dark:bg-black
|
||||||
flex items-center justify-center
|
flex items-center justify-center
|
||||||
transition-[top] drop-shadow
|
transition-[top] drop-shadow
|
||||||
${showOptionSidebar ? "-top-[60px]" : "-top-[2px] delay-200"}
|
${show ? "-top-[60px]" : "-top-[2px] delay-200"}
|
||||||
`}>
|
`}>
|
||||||
<svg
|
<svg
|
||||||
className="icon"
|
className="icon"
|
||||||
|
@ -1,86 +1,47 @@
|
|||||||
import React, { useContext, 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.tsx"
|
import { Header } from "./Header.tsx"
|
||||||
import IodVideo from "@/components/Option/VideoPlayer"
|
|
||||||
|
|
||||||
interface OptionLayoutContextType {
|
interface History {
|
||||||
showOptionSidebar: boolean
|
show: boolean
|
||||||
setShowOptionSidebar: (show: boolean) => void
|
setShow: (show: boolean) => void
|
||||||
showVideo: boolean
|
|
||||||
setShowVideo: (show: boolean) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const OptionLayoutContext = React.createContext<OptionLayoutContextType>({
|
export const HistoryContext = React.createContext<History>({
|
||||||
showOptionSidebar: true,
|
show: true,
|
||||||
setShowOptionSidebar: () => {},
|
setShow: () => {}
|
||||||
showVideo: true,
|
|
||||||
setShowVideo: () => {}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建自定义 hook 以便子组件使用
|
|
||||||
export const useOptionLayoutContext = () => {
|
|
||||||
const context = useContext(OptionLayoutContext)
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
"useOptionLayoutContext must be used within a OptionLayoutProvider"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
const OptionLayoutProvider = ({ children }: { children: React.ReactNode }) => {
|
|
||||||
const [showHistory, setShowHistory] = useState(true)
|
|
||||||
|
|
||||||
const [showVideo, setShowVideo] = useState<boolean>(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OptionLayoutContext.Provider
|
|
||||||
value={{
|
|
||||||
showOptionSidebar: showHistory,
|
|
||||||
setShowOptionSidebar: setShowHistory,
|
|
||||||
showVideo,
|
|
||||||
setShowVideo
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</OptionLayoutContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const OptionLayoutMain: React.FC<{ children: React.ReactNode }> = ({
|
|
||||||
children
|
|
||||||
}) => {
|
|
||||||
const { showVideo } = useOptionLayoutContext()
|
|
||||||
|
|
||||||
if (showVideo) {
|
|
||||||
return <IodVideo />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header />
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function OptionLayout({
|
export default function OptionLayout({
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
const [showHistory, setShowHistory] = useState(true)
|
||||||
const [openModelSettings, setOpenModelSettings] = useState(false)
|
const [openModelSettings, setOpenModelSettings] = useState(false)
|
||||||
|
|
||||||
|
const historyContextValue = {
|
||||||
|
show: showHistory,
|
||||||
|
setShow: setShowHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
const useToggle = useCallback(() => {
|
||||||
|
setShowHistory(!showHistory)
|
||||||
|
}, [showHistory])
|
||||||
|
|
||||||
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">*/}
|
||||||
{/*</div>*/}
|
{/*</div>*/}
|
||||||
{/* <div className="relative flex h-full flex-col items-center"> */}
|
{/* <div className="relative flex h-full flex-col items-center"> */}
|
||||||
<OptionLayoutProvider>
|
<HistoryContext.Provider value={historyContextValue}>
|
||||||
<OptionLayoutMain>{children}</OptionLayoutMain>
|
<Header setOpenModelSettings={setOpenModelSettings} />
|
||||||
</OptionLayoutProvider>
|
{children}
|
||||||
|
</HistoryContext.Provider>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
|
|
||||||
<CurrentChatModelSettings
|
<CurrentChatModelSettings
|
||||||
open={openModelSettings}
|
open={openModelSettings}
|
||||||
setOpen={setOpenModelSettings}
|
setOpen={setOpenModelSettings}
|
||||||
|
@ -2,7 +2,6 @@ import React from "react"
|
|||||||
|
|
||||||
import { PlaygroundForm } from "./PlaygroundForm"
|
import { PlaygroundForm } from "./PlaygroundForm"
|
||||||
import { PlaygroundChat } from "./PlaygroundChat"
|
import { PlaygroundChat } from "./PlaygroundChat"
|
||||||
import { PlaygroundSidebar } from "./PlaygroundSidebar.tsx"
|
|
||||||
import { useMessageOption } from "@/hooks/useMessageOption"
|
import { useMessageOption } from "@/hooks/useMessageOption"
|
||||||
import { webUIResumeLastChat } from "@/services/app"
|
import { webUIResumeLastChat } from "@/services/app"
|
||||||
|
|
||||||
@ -16,6 +15,7 @@ import { getLastUsedChatSystemPrompt } from "@/services/model-settings"
|
|||||||
import { useStoreChatModelSettings } from "@/store/model"
|
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 { PlaygroundHistory } from "@/components/Common/Playground/History.tsx"
|
||||||
import { PlaygroundIod } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
import { PlaygroundIod } from "@/components/Option/Playground/PlaygroundIod.tsx"
|
||||||
|
|
||||||
export const Playground = () => {
|
export const Playground = () => {
|
||||||
@ -139,7 +139,7 @@ export const Playground = () => {
|
|||||||
className={`relative flex gap-3 h-full items-center ${
|
className={`relative flex gap-3 h-full items-center ${
|
||||||
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]`}>
|
||||||
<PlaygroundSidebar />
|
<PlaygroundHistory />
|
||||||
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
|
<div className="h-full flex-1 overflow-x-hidden prose-lg flex flex-col items-center [&>*]:max-w-[848px] pt-[60px]">
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
@ -48,6 +48,9 @@ const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
const [detailMain, setDetailMain] = useState(<></>)
|
const [detailMain, setDetailMain] = useState(<></>)
|
||||||
|
|
||||||
const currentIodMessage = useMemo<AllIodRegistryEntry | undefined>(() => {
|
const currentIodMessage = useMemo<AllIodRegistryEntry | undefined>(() => {
|
||||||
|
console.log('messages', messages)
|
||||||
|
console.log("currentMessageId", currentMessageId)
|
||||||
|
console.log("iodLoading", iodLoading)
|
||||||
// loading 返回 undefined是为了避免,数据不足三个的情况
|
// loading 返回 undefined是为了避免,数据不足三个的情况
|
||||||
if (iodLoading || !messages.length) {
|
if (iodLoading || !messages.length) {
|
||||||
return undefined
|
return undefined
|
||||||
@ -63,6 +66,7 @@ const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
const currentMessage = messages?.find(
|
const currentMessage = messages?.find(
|
||||||
(message) => message.id === currentMessageId
|
(message) => message.id === currentMessageId
|
||||||
)
|
)
|
||||||
|
console.log("currentMessage", currentMessage)
|
||||||
return currentMessage?.iodSearch ? currentMessage.iodSources : undefined
|
return currentMessage?.iodSearch ? currentMessage.iodSources : undefined
|
||||||
}, [currentMessageId, messages, iodLoading])
|
}, [currentMessageId, messages, iodLoading])
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ type Props = {
|
|||||||
setSelectedModel: (model: string) => void
|
setSelectedModel: (model: string) => void
|
||||||
setSelectedSystemPrompt: (prompt: string) => void
|
setSelectedSystemPrompt: (prompt: string) => void
|
||||||
setSystemPrompt: (prompt: string) => void
|
setSystemPrompt: (prompt: string) => void
|
||||||
stopStreamingRequest: () => void
|
|
||||||
clearChat: () => void
|
clearChat: () => void
|
||||||
temporaryChat: boolean
|
temporaryChat: boolean
|
||||||
historyId: string
|
historyId: string
|
||||||
@ -47,7 +46,6 @@ export const Sidebar = ({
|
|||||||
setHistoryId,
|
setHistoryId,
|
||||||
setSelectedModel,
|
setSelectedModel,
|
||||||
setSelectedSystemPrompt,
|
setSelectedSystemPrompt,
|
||||||
stopStreamingRequest,
|
|
||||||
clearChat,
|
clearChat,
|
||||||
historyId,
|
historyId,
|
||||||
setSystemPrompt,
|
setSystemPrompt,
|
||||||
@ -142,40 +140,6 @@ export const Sidebar = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleHistoryClick = async (chat: any) => {
|
|
||||||
const db = new PageAssitDatabase()
|
|
||||||
const history = await db.getChatHistory(chat.id)
|
|
||||||
setHistoryId(chat.id)
|
|
||||||
setHistory(formatToChatHistory(history))
|
|
||||||
setMessages(formatToMessage(history))
|
|
||||||
stopStreamingRequest()
|
|
||||||
const isLastUsedChatModel =
|
|
||||||
await lastUsedChatModelEnabled()
|
|
||||||
if (isLastUsedChatModel) {
|
|
||||||
const currentChatModel = await getLastUsedChatModel(
|
|
||||||
chat.id
|
|
||||||
)
|
|
||||||
if (currentChatModel) {
|
|
||||||
setSelectedModel(currentChatModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const lastUsedPrompt =
|
|
||||||
await getLastUsedChatSystemPrompt(chat.id)
|
|
||||||
if (lastUsedPrompt) {
|
|
||||||
if (lastUsedPrompt.prompt_id) {
|
|
||||||
const prompt = await getPromptById(
|
|
||||||
lastUsedPrompt.prompt_id
|
|
||||||
)
|
|
||||||
if (prompt) {
|
|
||||||
setSelectedSystemPrompt(lastUsedPrompt.prompt_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSystemPrompt(lastUsedPrompt.prompt_content)
|
|
||||||
}
|
|
||||||
navigate("/")
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`overflow-y-auto z-99 ${temporaryChat ? "pointer-events-none opacity-50" : ""}`}>
|
className={`overflow-y-auto z-99 ${temporaryChat ? "pointer-events-none opacity-50" : ""}`}>
|
||||||
@ -217,7 +181,38 @@ export const Sidebar = ({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="flex-1 overflow-hidden break-all text-start truncate w-full"
|
className="flex-1 overflow-hidden break-all text-start truncate w-full"
|
||||||
onClick={() => handleHistoryClick(chat)}>
|
onClick={async () => {
|
||||||
|
const db = new PageAssitDatabase()
|
||||||
|
const history = await db.getChatHistory(chat.id)
|
||||||
|
setHistoryId(chat.id)
|
||||||
|
setHistory(formatToChatHistory(history))
|
||||||
|
setMessages(formatToMessage(history))
|
||||||
|
const isLastUsedChatModel =
|
||||||
|
await lastUsedChatModelEnabled()
|
||||||
|
if (isLastUsedChatModel) {
|
||||||
|
const currentChatModel = await getLastUsedChatModel(
|
||||||
|
chat.id
|
||||||
|
)
|
||||||
|
if (currentChatModel) {
|
||||||
|
setSelectedModel(currentChatModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lastUsedPrompt =
|
||||||
|
await getLastUsedChatSystemPrompt(chat.id)
|
||||||
|
if (lastUsedPrompt) {
|
||||||
|
if (lastUsedPrompt.prompt_id) {
|
||||||
|
const prompt = await getPromptById(
|
||||||
|
lastUsedPrompt.prompt_id
|
||||||
|
)
|
||||||
|
if (prompt) {
|
||||||
|
setSelectedSystemPrompt(lastUsedPrompt.prompt_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSystemPrompt(lastUsedPrompt.prompt_content)
|
||||||
|
}
|
||||||
|
navigate("/")
|
||||||
|
onClose()
|
||||||
|
}}>
|
||||||
<span className="flex-grow truncate">{chat.title}</span>
|
<span className="flex-grow truncate">{chat.title}</span>
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -1,410 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react"
|
|
||||||
import iodVideo from "@/assets/video.mp4"
|
|
||||||
import { useOptionLayoutContext } from "@/components/Layouts/Layout.tsx"
|
|
||||||
import {
|
|
||||||
ExpandOutlined,
|
|
||||||
PauseCircleOutlined,
|
|
||||||
PlayCircleOutlined
|
|
||||||
} from "@ant-design/icons"
|
|
||||||
import logo from "@/assets/logo.png"
|
|
||||||
|
|
||||||
const VideoPlayer = () => {
|
|
||||||
const { setShowVideo } = useOptionLayoutContext()
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
|
||||||
const controlsTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
||||||
const mouseMoveTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
||||||
const isPlayingRef = useRef(false)
|
|
||||||
|
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
|
||||||
const [currentTime, setCurrentTime] = useState(0)
|
|
||||||
const [duration, setDuration] = useState(0)
|
|
||||||
const [volume, setVolume] = useState(1)
|
|
||||||
const [isMuted, setIsMuted] = useState(false)
|
|
||||||
const [showControls, setShowControls] = useState(false)
|
|
||||||
const [isBuffering, setIsBuffering] = useState(false)
|
|
||||||
|
|
||||||
// 更新 isPlayingRef 当状态变化时
|
|
||||||
useEffect(() => {
|
|
||||||
isPlayingRef.current = isPlaying
|
|
||||||
}, [isPlaying])
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
const formatTime = (seconds: number) => {
|
|
||||||
if (isNaN(seconds)) return "00:00"
|
|
||||||
const minutes = Math.floor(seconds / 60)
|
|
||||||
const secs = Math.floor(seconds % 60)
|
|
||||||
return `${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理播放/暂停
|
|
||||||
const togglePlayPause = () => {
|
|
||||||
const video = videoRef.current
|
|
||||||
if (!video) return
|
|
||||||
|
|
||||||
if (isPlaying) {
|
|
||||||
video.pause()
|
|
||||||
setIsPlaying(false)
|
|
||||||
} else {
|
|
||||||
video.play().catch((error) => {
|
|
||||||
console.error("播放失败:", error)
|
|
||||||
})
|
|
||||||
setIsPlaying(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理音量变化
|
|
||||||
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const newVolume = parseFloat(e.target.value)
|
|
||||||
setVolume(newVolume)
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.volume = newVolume
|
|
||||||
setIsMuted(newVolume === 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换静音
|
|
||||||
const toggleMute = () => {
|
|
||||||
const newMuted = !isMuted
|
|
||||||
setIsMuted(newMuted)
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.muted = newMuted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 进度条点击处理
|
|
||||||
const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
const progressBar = e.currentTarget
|
|
||||||
const clickPosition = e.nativeEvent.offsetX
|
|
||||||
const progressBarWidth = progressBar.offsetWidth
|
|
||||||
if (duration > 0 && videoRef.current) {
|
|
||||||
const newTime = (clickPosition / progressBarWidth) * duration
|
|
||||||
videoRef.current.currentTime = newTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全屏切换
|
|
||||||
const toggleFullscreen = () => {
|
|
||||||
const videoContainer = containerRef.current
|
|
||||||
if (!videoContainer) return
|
|
||||||
|
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
if (videoContainer.requestFullscreen) {
|
|
||||||
videoContainer.requestFullscreen().catch((err) => {
|
|
||||||
console.error("全屏切换失败:", err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEnded = () => {
|
|
||||||
setIsPlaying(false)
|
|
||||||
setShowVideo(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制栏显示/隐藏 - 与原始HTML版本行为完全一致,添加防抖功能
|
|
||||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
// 清除之前的防抖定时器
|
|
||||||
if (mouseMoveTimerRef.current) {
|
|
||||||
clearTimeout(mouseMoveTimerRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新的防抖定时器
|
|
||||||
mouseMoveTimerRef.current = setTimeout(() => {
|
|
||||||
const container = containerRef.current
|
|
||||||
if (!container) return
|
|
||||||
|
|
||||||
const containerHeight = container.offsetHeight
|
|
||||||
const mouseY = e.clientY - container.getBoundingClientRect().top
|
|
||||||
|
|
||||||
// 如果鼠标在底部150px区域内
|
|
||||||
if (mouseY > containerHeight - 150) {
|
|
||||||
// 清除之前的隐藏定时器
|
|
||||||
if (controlsTimerRef.current) {
|
|
||||||
clearTimeout(controlsTimerRef.current)
|
|
||||||
}
|
|
||||||
// 立即显示控制器
|
|
||||||
setShowControls(true)
|
|
||||||
} else {
|
|
||||||
// 鼠标离开底部区域,设置定时器隐藏控制器
|
|
||||||
if (controlsTimerRef.current) {
|
|
||||||
clearTimeout(controlsTimerRef.current)
|
|
||||||
}
|
|
||||||
controlsTimerRef.current = setTimeout(() => {
|
|
||||||
setShowControls(false)
|
|
||||||
}, 300) // 300ms后隐藏
|
|
||||||
}
|
|
||||||
}, 10) // 10ms 防抖延迟
|
|
||||||
}
|
|
||||||
|
|
||||||
// 鼠标离开整个视频容器时立即隐藏控制器
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
// 清除防抖定时器
|
|
||||||
if (mouseMoveTimerRef.current) {
|
|
||||||
clearTimeout(mouseMoveTimerRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除控制栏隐藏定时器
|
|
||||||
if (controlsTimerRef.current) {
|
|
||||||
clearTimeout(controlsTimerRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 立即隐藏控制栏
|
|
||||||
setShowControls(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视频事件处理
|
|
||||||
useEffect(() => {
|
|
||||||
const video = videoRef.current
|
|
||||||
if (!video) return
|
|
||||||
|
|
||||||
const handleLoadedMetadata = () => {
|
|
||||||
setDuration(video.duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTimeUpdate = () => {
|
|
||||||
setCurrentTime(video.currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleWaiting = () => {
|
|
||||||
setIsBuffering(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePlaying = () => {
|
|
||||||
setIsBuffering(false)
|
|
||||||
setIsPlaying(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePause = () => {
|
|
||||||
if (!isBuffering) {
|
|
||||||
setIsPlaying(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
video.addEventListener("loadedmetadata", handleLoadedMetadata)
|
|
||||||
video.addEventListener("timeupdate", handleTimeUpdate)
|
|
||||||
video.addEventListener("waiting", handleWaiting)
|
|
||||||
video.addEventListener("playing", handlePlaying)
|
|
||||||
video.addEventListener("pause", handlePause)
|
|
||||||
video.addEventListener("ended", handleEnded)
|
|
||||||
|
|
||||||
// 组件挂载时尝试播放视频
|
|
||||||
const playVideo = async () => {
|
|
||||||
try {
|
|
||||||
await video.play()
|
|
||||||
setIsPlaying(true)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("自动播放失败:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const timer = setTimeout(playVideo, 100)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
video.removeEventListener("loadedmetadata", handleLoadedMetadata)
|
|
||||||
video.removeEventListener("timeupdate", handleTimeUpdate)
|
|
||||||
video.removeEventListener("waiting", handleWaiting)
|
|
||||||
video.removeEventListener("playing", handlePlaying)
|
|
||||||
video.removeEventListener("pause", handlePause)
|
|
||||||
video.removeEventListener("ended", handleEnded)
|
|
||||||
|
|
||||||
// 清除所有定时器
|
|
||||||
if (controlsTimerRef.current) {
|
|
||||||
clearTimeout(controlsTimerRef.current)
|
|
||||||
}
|
|
||||||
if (mouseMoveTimerRef.current) {
|
|
||||||
clearTimeout(mouseMoveTimerRef.current)
|
|
||||||
}
|
|
||||||
clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// 处理键盘事件
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (!videoRef.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (e.code) {
|
|
||||||
case "Space":
|
|
||||||
e.preventDefault()
|
|
||||||
const video = videoRef.current
|
|
||||||
if (!video) return
|
|
||||||
|
|
||||||
if (isPlayingRef.current) {
|
|
||||||
video.pause()
|
|
||||||
setIsPlaying(false)
|
|
||||||
} else {
|
|
||||||
video.play().catch((error) => {
|
|
||||||
console.error("播放失败:", error)
|
|
||||||
})
|
|
||||||
setIsPlaying(true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "ArrowLeft":
|
|
||||||
e.preventDefault()
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.currentTime = Math.max(
|
|
||||||
0,
|
|
||||||
videoRef.current.currentTime - 10
|
|
||||||
)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "ArrowRight":
|
|
||||||
e.preventDefault()
|
|
||||||
if (videoRef.current && duration) {
|
|
||||||
videoRef.current.currentTime = Math.min(
|
|
||||||
duration,
|
|
||||||
videoRef.current.currentTime + 10
|
|
||||||
)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "ArrowUp":
|
|
||||||
e.preventDefault()
|
|
||||||
setVolume((prev) => {
|
|
||||||
const newVolume = Math.min(1, prev + 0.1)
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.volume = newVolume
|
|
||||||
setIsMuted(newVolume === 0)
|
|
||||||
}
|
|
||||||
return newVolume
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "ArrowDown":
|
|
||||||
e.preventDefault()
|
|
||||||
setVolume((prev) => {
|
|
||||||
const newVolume = Math.max(0, prev - 0.1)
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.volume = newVolume
|
|
||||||
setIsMuted(newVolume === 0)
|
|
||||||
}
|
|
||||||
return newVolume
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "KeyM":
|
|
||||||
e.preventDefault()
|
|
||||||
toggleMute()
|
|
||||||
break
|
|
||||||
case "KeyF":
|
|
||||||
e.preventDefault()
|
|
||||||
toggleFullscreen()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 键盘事件监听
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener("keydown", handleKeyDown)
|
|
||||||
|
|
||||||
if (containerRef.current) {
|
|
||||||
containerRef.current.tabIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleKeyDown)
|
|
||||||
}
|
|
||||||
}, [duration])
|
|
||||||
|
|
||||||
// 计算进度条百分比
|
|
||||||
const progressPercent = duration ? (currentTime / duration) * 100 : 0
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className="relative w-full h-screen bg-black flex justify-center items-center"
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseLeave={handleMouseLeave}>
|
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
className="w-full h-full object-cover bg-black"
|
|
||||||
onClick={togglePlayPause}
|
|
||||||
playsInline
|
|
||||||
preload="auto">
|
|
||||||
<source src={iodVideo} type="video/mp4" />
|
|
||||||
您的浏览器不支持HTML5视频播放
|
|
||||||
</video>
|
|
||||||
|
|
||||||
{/* 暂停时的遮罩层 */}
|
|
||||||
{!isPlaying && !isBuffering && (
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center cursor-pointer"
|
|
||||||
onClick={togglePlayPause}>
|
|
||||||
<PlayCircleOutlined className="text-white text-6xl opacity-80" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 缓冲提示 */}
|
|
||||||
{isBuffering && (
|
|
||||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white text-sm bg-black bg-opacity-50 px-4 py-2 rounded">
|
|
||||||
缓冲中...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{/* 控制栏 - 使用与原始HTML相同的类名和行为 */}
|
|
||||||
<div
|
|
||||||
className={`absolute bottom-0 left-0 w-full bg-gradient-to-t from-black to-transparent p-4 transition-all duration-300 ease-in-out flex flex-col gap-2.5 ${showControls ? "bottom-0" : "-bottom-32"}`}>
|
|
||||||
<div className="flex items-center justify-end gap-2 cursor-pointer" onClick={handleEnded}>
|
|
||||||
{<img src={logo} alt="logo" className="w-8" />}
|
|
||||||
<h2 className="text-xl font-bold text-white dark:text-zinc-300 mr-3">
|
|
||||||
<span className="text-[#d30100]">数联网</span>科创智能体
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="w-full h-1.5 bg-white bg-opacity-20 rounded cursor-pointer mb-2.5"
|
|
||||||
onClick={handleProgressClick}>
|
|
||||||
<div
|
|
||||||
className="h-full bg-gradient-to-r from-orange-500 to-pink-600 rounded transition-all duration-100"
|
|
||||||
style={{ width: `${progressPercent}%` }}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<button
|
|
||||||
className="bg-transparent border-none text-white text-lg cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
|
||||||
onClick={togglePlayPause}>
|
|
||||||
{isPlaying ? (
|
|
||||||
<PauseCircleOutlined className="text-2xl" />
|
|
||||||
) : (
|
|
||||||
<PlayCircleOutlined className="text-2xl" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span className="text-white text-sm min-w-[100px] text-center">
|
|
||||||
<span>{formatTime(currentTime)}</span> /
|
|
||||||
<span>{formatTime(duration)}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className="flex items-center ml-auto">
|
|
||||||
<button
|
|
||||||
className="bg-transparent border-none text-white text-2xl cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
|
||||||
onClick={toggleMute}>
|
|
||||||
{isMuted ? "🔇" : "🔊"}
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="1"
|
|
||||||
step="0.1"
|
|
||||||
value={isMuted ? 0 : volume}
|
|
||||||
onChange={handleVolumeChange}
|
|
||||||
className="w-20 h-1.5 ml-2.5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="bg-transparent border-none text-white text-lg cursor-pointer p-1 rounded-full w-12 h-12 flex items-center justify-center hover:bg-white hover:bg-opacity-20 transition-colors"
|
|
||||||
onClick={toggleFullscreen}>
|
|
||||||
<ExpandOutlined className="text-2xl" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VideoPlayer
|
|
@ -60,7 +60,7 @@ export const importPageAssistData = async (file: File) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(data?.iod) {
|
if(data?.iod) {
|
||||||
IodDb.getInstance().insertIodConnection(data.iod.connection)
|
IodDb.getInstance().insertIodConnection(data.iod)
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success("Data imported successfully")
|
message.success("Data imported successfully")
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import OptionLayout from "~/components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import IodVideo from "@/components/Option/VideoPlayer/index.tsx"
|
|
||||||
import { Playground } from "~/components/Option/Playground/Playground"
|
import { Playground } from "~/components/Option/Playground/Playground"
|
||||||
|
|
||||||
const OptionIndex = () => {
|
const OptionIndex = () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user