From 0bf6e2c53763dfad1003d172afd1f6e84057065b Mon Sep 17 00:00:00 2001 From: liuhuapiaoyuan <278780765@qq.com> Date: Fri, 25 Oct 2024 14:18:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E5=8A=A8=E7=AB=AF=E5=85=BC?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 38 +++++++ frontend/src/App.tsx | 13 ++- frontend/src/components/audio.tsx | 30 ++++-- frontend/src/components/content.tsx | 9 +- frontend/src/components/episode.tsx | 22 ++-- frontend/src/components/menu.tsx | 4 +- frontend/src/components/mobile-menu.tsx | 52 +++++++++ frontend/src/components/transcript.tsx | 18 ++-- frontend/src/components/ui/sheet.tsx | 138 ++++++++++++++++++++++++ frontend/src/index.css | 11 ++ frontend/tailwind.config.js | 4 +- 12 files changed, 301 insertions(+), 39 deletions(-) create mode 100644 frontend/src/components/mobile-menu.tsx create mode 100644 frontend/src/components/ui/sheet.tsx diff --git a/frontend/package.json b/frontend/package.json index aad42c6..3d60828 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@madzadev/audio-player": "^2.1.14", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 95e28c7..2dc25e9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@madzadev/audio-player': specifier: ^2.1.14 version: 2.1.14 + '@radix-ui/react-dialog': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.2 version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -559,6 +562,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.1.2': + resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -2787,6 +2803,28 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ad9483e..ecfb0c3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,6 +4,7 @@ import Menu from "./components/menu"; import { useJsonData } from "./hooks/useJsonData"; import { useStreamText } from './hooks/useStreamText'; import { BASE_URL } from "./lib/constant"; +import MobileMenu from "./components/mobile-menu"; function App() { const [isGenerating, setIsGenerating] = useState(false); @@ -23,7 +24,7 @@ function App() { data: podInfoData, error: podInfoError, isLoading: isPodInfoLoading, - fetchJsonData: fetchPodInfo, + fetchJsonData: fetchPodInfo, } = useJsonData(); @@ -37,13 +38,13 @@ function App() { return (
- + /> } />
diff --git a/frontend/src/components/audio.tsx b/frontend/src/components/audio.tsx index f92e729..0ce9a01 100644 --- a/frontend/src/components/audio.tsx +++ b/frontend/src/components/audio.tsx @@ -26,27 +26,36 @@ export default function Audio({ audioUrl, isAudioLoading, audioError }: { audioU } }; + const MOBILE_TEMPLATE_AREA = { + "trackInfo": "row1-1", + "playButton": "row2-1", + "repeatType": "row2-2", + "playList": "row2-3", + "progress": "row3-1", + "trackTimeDuration": "row1-3", + "trackTimeCurrent": "row1-2", + }; return ( -
+
- + {isAudioLoading ? ( -
-
- -
-
+
+
+ +
+

Generating conversation...

This may take a few minutes. No need to stick around!

-
+
) : audioError ? (

音频生成失败: {audioError}

) : audioUrl ? ( -
+
void; + mobileMenu?:React.ReactNode; } export default function Content({ formData, @@ -35,7 +36,8 @@ export default function Content({ setIsGenerating, activeTab, setActiveTab, -}: ContentProps) { + mobileMenu +}:ContentProps ) { const { error: audioError, isLoading: isAudioLoading, @@ -93,9 +95,10 @@ export default function Content({ isPodInfoLoading={isPodInfoLoading} podInfoError={podInfoError} podInfoData={podInfoData} + mobileMenu={mobileMenu} />
-
+
- {podInfoData?.title + {podInfoData?.title { @@ -34,7 +37,7 @@ export default function Episode({ isPodInfoLoading, podInfoError, podInfoData }: Error - {podInfoError || "An error occurred while loading the episode data."} + {podInfoError || "An error occurred while loading the episode data."} ) @@ -43,9 +46,10 @@ export default function Episode({ isPodInfoLoading, podInfoError, podInfoData }: !isPodInfoLoading && !podInfoError && (

{podInfoData?.title || "播客标题"}

-

主讲人: {podInfoData?.host_name || "未知"}

-
- )} +

主讲人: {podInfoData?.host_name || "未知"}

+
+ )} + {mobileMenu}
) } diff --git a/frontend/src/components/menu.tsx b/frontend/src/components/menu.tsx index 24830d7..82449b8 100644 --- a/frontend/src/components/menu.tsx +++ b/frontend/src/components/menu.tsx @@ -9,7 +9,7 @@ import { useSpeeker } from '@/hooks/useSpeeker'; const MAX_FILE_SIZE = 1 * 1024 * 1024; // 5MB in bytes const DEMO_PDF_URL = '/demo.pdf'; // 替换为你的演示 PDF 文件的实际路径 -export default function Menu({ handleGenerate, isGenerating }: { handleGenerate: (formData: FormData) => void, isGenerating: boolean }) { +export default function Menu({ handleGenerate,className, isGenerating }: { className?:string,handleGenerate: (formData: FormData) => void, isGenerating: boolean }) { const [pdfFile, setPdfFile] = useState(null); const [textInput, setTextInput] = useState(''); const [tone, setTone] = useState('neutral'); @@ -69,7 +69,7 @@ export default function Menu({ handleGenerate, isGenerating }: { handleGenerate: }; return ( -
+

上传 PDF *

diff --git a/frontend/src/components/mobile-menu.tsx b/frontend/src/components/mobile-menu.tsx new file mode 100644 index 0000000..28851f0 --- /dev/null +++ b/frontend/src/components/mobile-menu.tsx @@ -0,0 +1,52 @@ +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" +import Menu from "./menu" +import { Button } from "./ui/button" +import { Sparkles } from "lucide-react" +import { useState } from "react" + +/** + * smallest component possible for mobile menu + * @param props + * @returns + */ +export default function MobileMenu( + { handleGenerate, + isGenerating }: { handleGenerate: (formData: FormData) => void, isGenerating: boolean }) { + const [open,setOpen] = useState(false) + return + + + + + + 开始制作 + +
+ { + setOpen(false) + return handleGenerate(data) + }} + className="!border-none p-0 m-0 !shadow-none" /> +
+
+
+ +} \ No newline at end of file diff --git a/frontend/src/components/transcript.tsx b/frontend/src/components/transcript.tsx index 8a46f43..37a1a7b 100644 --- a/frontend/src/components/transcript.tsx +++ b/frontend/src/components/transcript.tsx @@ -45,8 +45,8 @@ export default function Transcript({ }, [transcriptTextChunks]); return ( -
- +
+
- - - + + { renderContent( @@ -78,9 +76,9 @@ export default function Transcript({ - - - + + + , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 6aef233..eed21a3 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,6 +10,17 @@ --rm-audio-player-interface-container: #fff; --primary: 240 5.9% 10%; } +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} .kuNzeN img { border-radius: 100% !important; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 86c0e3d..3ebf778 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -12,8 +12,8 @@ export default { sm: 'calc(var(--radius) - 4px)' }, colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', + background: 'var(--background)', + foreground: 'var(--foreground)', card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))'