) {
)
}
-function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
+function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
const { formDescriptionId } = useFormField()
return (
)
}
-function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
+function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const { error, formMessageId } = useFormField()
- const body = error ? String(error?.message ?? "") : props.children
+ const body = error ? String(error?.message ?? '') : props.children
if (!body) {
return null
@@ -145,7 +130,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
{body}
diff --git a/BillNote_frontend/src/components/ui/input.tsx b/BillNote_frontend/src/components/ui/input.tsx
index 03295ca..e9d655c 100644
--- a/BillNote_frontend/src/components/ui/input.tsx
+++ b/BillNote_frontend/src/components/ui/input.tsx
@@ -1,16 +1,16 @@
-import * as React from "react"
+import * as React from 'react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
) {
+function Label({ className, ...props }: React.ComponentProps) {
return (
) {
return (
@@ -36,11 +36,9 @@ function ScrollBar({
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
- "flex touch-none p-px transition-colors select-none",
- orientation === "vertical" &&
- "h-full w-2.5 border-l border-l-transparent",
- orientation === "horizontal" &&
- "h-2.5 flex-col border-t border-t-transparent",
+ 'flex touch-none p-px transition-colors select-none',
+ orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent',
+ orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent',
className
)}
{...props}
diff --git a/BillNote_frontend/src/components/ui/select.tsx b/BillNote_frontend/src/components/ui/select.tsx
index 51f466e..2c63404 100644
--- a/BillNote_frontend/src/components/ui/select.tsx
+++ b/BillNote_frontend/src/components/ui/select.tsx
@@ -1,34 +1,28 @@
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+import * as React from 'react'
+import * as SelectPrimitive from '@radix-ui/react-select'
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils'
-function Select({
- ...props
-}: React.ComponentProps) {
+function Select({ ...props }: React.ComponentProps) {
return
}
-function SelectGroup({
- ...props
-}: React.ComponentProps) {
+function SelectGroup({ ...props }: React.ComponentProps) {
return
}
-function SelectValue({
- ...props
-}: React.ComponentProps) {
+function SelectValue({ ...props }: React.ComponentProps) {
return
}
function SelectTrigger({
className,
- size = "default",
+ size = 'default',
children,
...props
}: React.ComponentProps & {
- size?: "sm" | "default"
+ size?: 'sm' | 'default'
}) {
return (
) {
return (
@@ -59,9 +53,9 @@ function SelectContent({
{children}
@@ -83,14 +77,11 @@ function SelectContent({
)
}
-function SelectLabel({
- className,
- ...props
-}: React.ComponentProps) {
+function SelectLabel({ className, ...props }: React.ComponentProps) {
return (
)
@@ -127,7 +118,7 @@ function SelectSeparator({
return (
)
@@ -140,10 +131,7 @@ function SelectScrollUpButton({
return (
@@ -158,10 +146,7 @@ function SelectScrollDownButton({
return (
diff --git a/BillNote_frontend/src/components/ui/sonner.tsx b/BillNote_frontend/src/components/ui/sonner.tsx
index cd62aff..2bc2d99 100644
--- a/BillNote_frontend/src/components/ui/sonner.tsx
+++ b/BillNote_frontend/src/components/ui/sonner.tsx
@@ -1,18 +1,18 @@
-import { useTheme } from "next-themes"
-import { Toaster as Sonner, ToasterProps } from "sonner"
+import { useTheme } from 'next-themes'
+import { Toaster as Sonner, ToasterProps } from 'sonner'
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const { theme = 'system' } = useTheme()
return (
) {
+function Tooltip({ ...props }: React.ComponentProps) {
return (
@@ -26,9 +24,7 @@ function Tooltip({
)
}
-function TooltipTrigger({
- ...props
-}: React.ComponentProps) {
+function TooltipTrigger({ ...props }: React.ComponentProps) {
return
}
@@ -44,7 +40,7 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
- "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
+ 'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
className
)}
{...props}
diff --git a/BillNote_frontend/src/hooks/useTaskPolling.ts b/BillNote_frontend/src/hooks/useTaskPolling.ts
index ca392fb..b821820 100644
--- a/BillNote_frontend/src/hooks/useTaskPolling.ts
+++ b/BillNote_frontend/src/hooks/useTaskPolling.ts
@@ -1,46 +1,45 @@
// hooks/useTaskPolling.ts
-import { useEffect } from "react"
-import { useTaskStore } from "@/store/taskStore"
-import {get_task_status} from "@/services/note.ts";
+import { useEffect } from 'react'
+import { useTaskStore } from '@/store/taskStore'
+import { get_task_status } from '@/services/note.ts'
export const useTaskPolling = (interval = 3000) => {
- const tasks = useTaskStore(state => state.tasks)
- const updateTaskContent = useTaskStore(state => state.updateTaskContent)
- const removeTask=useTaskStore(state=>state.removeTask)
- useEffect(() => {
- const timer = setInterval(async () => {
- const pendingTasks = tasks.filter(
- (task) => task.status === "PENDING" || task.status === "running"
- )
+ const tasks = useTaskStore(state => state.tasks)
+ const updateTaskContent = useTaskStore(state => state.updateTaskContent)
+ const removeTask = useTaskStore(state => state.removeTask)
+ useEffect(() => {
+ const timer = setInterval(async () => {
+ const pendingTasks = tasks.filter(
+ task => task.status === 'PENDING' || task.status === 'running'
+ )
- for (const task of pendingTasks) {
- try {
- console.log(task)
- const res = await get_task_status(task.id)
- const {status}=res.data
+ for (const task of pendingTasks) {
+ try {
+ console.log(task)
+ const res = await get_task_status(task.id)
+ const { status } = res.data
- if (status && status !== task.status) {
- if (status === "SUCCESS") {
- const { markdown, transcript, audio_meta } = res.data.result
+ if (status && status !== task.status) {
+ if (status === 'SUCCESS') {
+ const { markdown, transcript, audio_meta } = res.data.result
- updateTaskContent(task.id, {
- status,
- markdown,
- transcript,
- audioMeta: audio_meta,
- })
- } else {
- updateTaskStatus(task.id, status)
- }
- }
- } catch (e) {
- console.error("❌ 任务轮询失败:", e)
- removeTask(task.id)
-
- }
+ updateTaskContent(task.id, {
+ status,
+ markdown,
+ transcript,
+ audioMeta: audio_meta,
+ })
+ } else {
+ updateTaskStatus(task.id, status)
}
- }, interval)
+ }
+ } catch (e) {
+ console.error('❌ 任务轮询失败:', e)
+ removeTask(task.id)
+ }
+ }
+ }, interval)
- return () => clearInterval(timer)
- }, [interval, tasks])
+ return () => clearInterval(timer)
+ }, [interval, tasks])
}
diff --git a/BillNote_frontend/src/index.css b/BillNote_frontend/src/index.css
index 61b11f3..1f0280c 100644
--- a/BillNote_frontend/src/index.css
+++ b/BillNote_frontend/src/index.css
@@ -1,5 +1,5 @@
-@import "tailwindcss";
-@import "tw-animate-css";
+@import 'tailwindcss';
+@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
@@ -11,7 +11,7 @@
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
- --primary: #3C77FB;
+ --primary: #3c77fb;
--primary-light: #e0eeff;
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
@@ -46,8 +46,8 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
- --primary: #3C77FB;
- --primary-light:#e0eeff;
+ --primary: #3c77fb;
+ --primary-light: #e0eeff;
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
diff --git a/BillNote_frontend/src/indexa.css b/BillNote_frontend/src/indexa.css
index f024e57..14a900f 100644
--- a/BillNote_frontend/src/indexa.css
+++ b/BillNote_frontend/src/indexa.css
@@ -12,7 +12,7 @@
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
- --primary: #3C77FB;
+ --primary: #3c77fb;
--primary-light: #e0eeff;
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
@@ -47,8 +47,8 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
- --primary: #3C77FB;
- --primary-light:#e0eeff;
+ --primary: #3c77fb;
+ --primary-light: #e0eeff;
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
diff --git a/BillNote_frontend/src/layouts/HomeLayout.tsx b/BillNote_frontend/src/layouts/HomeLayout.tsx
index da93356..d1a323f 100644
--- a/BillNote_frontend/src/layouts/HomeLayout.tsx
+++ b/BillNote_frontend/src/layouts/HomeLayout.tsx
@@ -1,43 +1,71 @@
-import type { FC, ReactNode } from 'react'
-import { Button } from "@/components/ui/button.tsx"
+import React, { FC } from 'react'
+import { SlidersHorizontal } from 'lucide-react'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip.tsx'
-interface HomeLayoutProps {
- form: ReactNode
- preview: ReactNode
+import { useState } from 'react'
+import { Link } from 'react-router-dom'
+
+interface IProps {
+ NoteForm: React.ReactNode
+ Preview: React.ReactNode
}
+const HomeLayout: FC = ({ NoteForm, Preview }) => {
+ const [, setShowSettings] = useState(false)
-const HomeLayout: FC = ({ form, preview }) => {
- return (
-
-
- {/* 左侧部分:Header + 表单 */}
-
-
- {/* 右侧预览区域 */}
-
- {preview}
-
+ return (
+
+
+ {/* 左侧部分:Header + 表单 */}
+
- )
+ {/* 表单内容 */}
+
+ {/**/}
+ {NoteForm}
+
+
+
+ {/* 右侧预览区域 */}
+
+ {/**/}
+ {Preview}
+
+
+
+ {/* 页脚 */}
+ {/*
*/}
+
+ )
}
export default HomeLayout
diff --git a/BillNote_frontend/src/layouts/RootLayout.tsx b/BillNote_frontend/src/layouts/RootLayout.tsx
index f92b5bb..2b3684e 100644
--- a/BillNote_frontend/src/layouts/RootLayout.tsx
+++ b/BillNote_frontend/src/layouts/RootLayout.tsx
@@ -1,32 +1,32 @@
-import type { ReactNode, FC } from "react"
+import type { ReactNode, FC } from 'react'
// import "@/global.css"
import { Toaster } from 'react-hot-toast'
interface RootLayoutProps {
- children: ReactNode
+ children: ReactNode
}
export const metadata = {
- title: "BiliNote - 视频笔记生成器",
- description: "通过视频链接结合大模型自动生成对应的笔记",
+ title: 'BiliNote - 视频笔记生成器',
+ description: '通过视频链接结合大模型自动生成对应的笔记',
}
const RootLayout: FC
= ({ children }) => {
- return (
-
-
- {children}
-
- )
+ return (
+
+
+ {children}
+
+ )
}
export default RootLayout
diff --git a/BillNote_frontend/src/layouts/SettingLayout.tsx b/BillNote_frontend/src/layouts/SettingLayout.tsx
new file mode 100644
index 0000000..6895c76
--- /dev/null
+++ b/BillNote_frontend/src/layouts/SettingLayout.tsx
@@ -0,0 +1,63 @@
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip.tsx'
+import { Link, Outlet } from 'react-router-dom'
+import { SlidersHorizontal } from 'lucide-react'
+import React from 'react'
+interface ISettingLayoutProps {
+ Menu: React.ReactNode
+}
+const SettingLayout = ({ Menu }: ISettingLayoutProps) => {
+ return (
+
+
+ {/* 左侧部分:Header + 表单 */}
+
+
+ {/* 右侧预览区域 */}
+
+
+
+
+
+ )
+}
+export default SettingLayout
diff --git a/BillNote_frontend/src/lib/utils.ts b/BillNote_frontend/src/lib/utils.ts
index bd0c391..fed2fe9 100644
--- a/BillNote_frontend/src/lib/utils.ts
+++ b/BillNote_frontend/src/lib/utils.ts
@@ -1,5 +1,5 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { clsx, type ClassValue } from 'clsx'
+import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
diff --git a/BillNote_frontend/src/main.tsx b/BillNote_frontend/src/main.tsx
index dfd8dd2..18f4d13 100644
--- a/BillNote_frontend/src/main.tsx
+++ b/BillNote_frontend/src/main.tsx
@@ -2,12 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
-import RootLayout from "./layouts/RootLayout.tsx";
+import RootLayout from './layouts/RootLayout.tsx'
createRoot(document.getElementById('root')!).render(
-
-
-
-
- ,
+
+
+
+
+
)
diff --git a/BillNote_frontend/src/pages/Home.tsx b/BillNote_frontend/src/pages/Home.tsx
deleted file mode 100644
index f721ad3..0000000
--- a/BillNote_frontend/src/pages/Home.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React,{FC,useEffect,useState} from "react";
-import HomeLayout from "@/layouts/HomeLayout.tsx";
-import NoteForm from '@/pages/components/NoteForm'
-import MarkdownViewer from '@/pages/components/MarkdownViewer'
-import NoteFormWrapper from "@/pages/components/NoteFormWrapper.tsx";
-import {get_task_status} from "@/services/note.ts";
-import {useTaskStore} from "@/store/taskStore";
-type ViewStatus = 'idle' | 'loading' | 'success'
-export const HomePage:FC =()=>{
- const tasks = useTaskStore((state) => state.tasks)
- const currentTaskId = useTaskStore((state) => state.currentTaskId)
-
- const currentTask = tasks.find((t) => t.id === currentTaskId)
-
- const [status, setStatus] = useState('idle')
-
- const content = currentTask?.markdown || ''
-
- useEffect(() => {
- if (!currentTask) {
- setStatus('idle')
- } else if (currentTask.status === 'PENDING') {
- setStatus('loading')
- } else if (currentTask.status === 'SUCCESS') {
- setStatus('success')
- }
- }, [currentTask])
-
- // useEffect( () => {
- // get_task_status('d4e87938-c066-48a0-bbd5-9bec40d53354').then(res=>{
- // console.log('res1',res)
- // setContent(res.data.result.markdown)
- // })
- // }, [tasks]);
- return (
- }
- preview={}
-
- />
- )
-}
\ No newline at end of file
diff --git a/BillNote_frontend/src/pages/HomePage/Home.tsx b/BillNote_frontend/src/pages/HomePage/Home.tsx
new file mode 100644
index 0000000..3655c0f
--- /dev/null
+++ b/BillNote_frontend/src/pages/HomePage/Home.tsx
@@ -0,0 +1,39 @@
+import { FC, useEffect, useState } from 'react'
+import HomeLayout from '@/layouts/HomeLayout.tsx'
+import NoteForm from '@/pages/HomePage/components/NoteForm.tsx'
+import MarkdownViewer from '@/pages/HomePage/components/MarkdownViewer.tsx'
+import { useTaskStore } from '@/store/taskStore'
+type ViewStatus = 'idle' | 'loading' | 'success'
+export const HomePage: FC = () => {
+ const tasks = useTaskStore(state => state.tasks)
+ const currentTaskId = useTaskStore(state => state.currentTaskId)
+
+ const currentTask = tasks.find(t => t.id === currentTaskId)
+
+ const [status, setStatus] = useState('idle')
+
+ const content = currentTask?.markdown || ''
+
+ useEffect(() => {
+ if (!currentTask) {
+ setStatus('idle')
+ } else if (currentTask.status === 'PENDING') {
+ setStatus('loading')
+ } else if (currentTask.status === 'SUCCESS') {
+ setStatus('success')
+ }
+ }, [currentTask])
+
+ // useEffect( () => {
+ // get_task_status('d4e87938-c066-48a0-bbd5-9bec40d53354').then(res=>{
+ // console.log('res1',res)
+ // setContent(res.data.result.markdown)
+ // })
+ // }, [tasks]);
+ return (
+ }
+ Preview={}
+ />
+ )
+}
diff --git a/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx
new file mode 100644
index 0000000..5d322f1
--- /dev/null
+++ b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx
@@ -0,0 +1,165 @@
+import { useState } from 'react'
+import ReactMarkdown from 'react-markdown'
+import { Button } from '@/components/ui/button.tsx'
+import { Copy, Download, FileText, ArrowRight } from 'lucide-react'
+import { toast } from 'sonner' // 你可以换成自己的通知组件
+
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
+import { solarizedlight as codeStyle } from 'react-syntax-highlighter/dist/cjs/styles/prism'
+import 'github-markdown-css/github-markdown-light.css'
+import { FC } from 'react'
+import Loading from '@/components/Lottie/Loading.tsx'
+import Idle from '@/components/Lottie/Idle.tsx'
+import { useTaskStore } from '@/store/taskStore'
+interface MarkdownViewerProps {
+ content: string
+ status: 'idle' | 'loading' | 'success'
+}
+
+const MarkdownViewer: FC = ({ content, status }) => {
+ const [copied, setCopied] = useState(false)
+ const getCurrentTask = useTaskStore.getState().getCurrentTask
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(content)
+ setCopied(true)
+ toast.success('已复制到剪贴板')
+ setTimeout(() => setCopied(false), 2000)
+ } catch (e) {
+ toast.error(`复制失败${e}`)
+ toast.error('复制失败', e)
+ }
+ }
+
+ const handleDownload = () => {
+ const currentTask = getCurrentTask()
+ const currentTaskName = currentTask?.audioMeta.title
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' })
+ const link = document.createElement('a')
+ link.href = URL.createObjectURL(blob)
+ link.download = `${currentTaskName}.md`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ }
+ if (status === 'loading') {
+ return (
+
+
+
+
正在生成笔记,请稍候…
+
这可能需要几秒钟时间,取决于视频长度
+
+
+ )
+ } else if (status === 'idle') {
+ return (
+
+
+
+
+
输入视频链接并点击“生成笔记”
+
支持哔哩哔哩、YouTube 等视频平台
+
+
+ )
+ }
+
+ return (
+
+ {/* 顶部操作栏 */}
+
+
+
+ 笔记内容
+
+
+
+
+
+
+
+ {/* 滚动容器 */}
+
+
+ {(content && content != 'loading') || content != 'empty' ? (
+
+ {' '}
+
+
+ {codeContent}
+
+
+
+ )
+ }
+
+ return (
+
+ {children}
+
+ )
+ },
+ }}
+ >
+ {content}
+
+
+ ) : (
+
+
+
+
输入视频链接并点击"生成笔记"按钮
+
支持哔哩哔哩、YouTube等视频网站
+
+
+ )}
+
+ {/**/}
+ {/* {content ? (*/}
+ {/* */}
+ {/* ) : (*/}
+ {/* <>*/}
+ {/*
*/}
+ {/*
输入视频链接并点击"生成笔记"按钮
*/}
+ {/*
支持哔哩哔哩、YouTube、腾讯视频和爱奇艺
*/}
+ {/* >*/}
+ {/* )}*/}
+ {/*
*/}
+
+ )
+}
+
+export default MarkdownViewer
diff --git a/BillNote_frontend/src/pages/HomePage/components/NoteForm.tsx b/BillNote_frontend/src/pages/HomePage/components/NoteForm.tsx
new file mode 100644
index 0000000..f879e3b
--- /dev/null
+++ b/BillNote_frontend/src/pages/HomePage/components/NoteForm.tsx
@@ -0,0 +1,263 @@
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form.tsx'
+import { Input } from '@/components/ui/input.tsx'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select.tsx'
+import { Button } from '@/components/ui/button.tsx'
+import { Checkbox } from '@/components/ui/checkbox.tsx'
+import { useForm } from 'react-hook-form'
+import { z } from 'zod'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { Info, Clock, Loader2 } from 'lucide-react'
+
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip.tsx'
+import { generateNote } from '@/services/note.ts'
+import { useTaskStore } from '@/store/taskStore'
+import NoteHistory from '@/pages/HomePage/components/NoteHistory.tsx'
+
+// ✅ 定义表单 schema
+const formSchema = z.object({
+ video_url: z.string().url('请输入正确的视频链接'),
+ platform: z.string().nonempty('请选择平台'),
+ quality: z.enum(['fast', 'medium', 'slow'], {
+ required_error: '请选择音频质量',
+ }),
+ screenshot: z.boolean().optional(),
+ link: z.boolean().optional(),
+})
+
+type NoteFormValues = z.infer
+
+const NoteForm = () => {
+ useTaskStore(state => state.tasks)
+ const setCurrentTask = useTaskStore(state => state.setCurrentTask)
+ const currentTaskId = useTaskStore(state => state.currentTaskId)
+ const getCurrentTask = useTaskStore(state => state.getCurrentTask)
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ video_url: '',
+ platform: 'bilibili',
+ quality: 'medium', // 默认中等质量
+ screenshot: false,
+ },
+ })
+
+ const isGenerating = () => {
+ console.log('🚀 isGenerating', getCurrentTask()?.status)
+ return getCurrentTask()?.status === 'PENDING'
+ }
+
+ const onSubmit = async (data: NoteFormValues) => {
+ console.log('🎯 提交内容:', data)
+ await generateNote({
+ video_url: data.video_url,
+ platform: data.platform,
+ quality: data.quality,
+ screenshot: data.screenshot,
+ link: data.link,
+ })
+ }
+
+ return (
+
+
+
+
+ {/*生成历史 */}
+
+
+
生成历史
+
+
+
+
+
+ {/* 添加一些额外的说明或功能介绍 */}
+
+
功能介绍
+
+ -
+ •
+ 自动提取视频内容,生成结构化笔记
+
+ -
+ •
+ 支持多个视频平台,包括哔哩哔哩、YouTube等
+
+ -
+ •
+ 一键复制笔记,支持Markdown格式
+
+ -
+ •
+ 可选择是否插入图片
+
+
+
+
+ )
+}
+
+export default NoteForm
diff --git a/BillNote_frontend/src/pages/HomePage/components/NoteFormWrapper.tsx b/BillNote_frontend/src/pages/HomePage/components/NoteFormWrapper.tsx
new file mode 100644
index 0000000..b47eafe
--- /dev/null
+++ b/BillNote_frontend/src/pages/HomePage/components/NoteFormWrapper.tsx
@@ -0,0 +1,15 @@
+import { useForm } from 'react-hook-form'
+import { Form } from '@/components/ui/form.tsx'
+import NoteForm from './NoteForm.tsx'
+
+const NoteFormWrapper = () => {
+ const form = useForm()
+
+ return (
+
+ )
+}
+
+export default NoteFormWrapper
diff --git a/BillNote_frontend/src/pages/HomePage/components/NoteHistory.tsx b/BillNote_frontend/src/pages/HomePage/components/NoteHistory.tsx
new file mode 100644
index 0000000..1e7be39
--- /dev/null
+++ b/BillNote_frontend/src/pages/HomePage/components/NoteHistory.tsx
@@ -0,0 +1,106 @@
+import { useTaskStore } from '@/store/taskStore'
+import { FC } from 'react'
+import { ScrollArea } from '@/components/ui/scroll-area.tsx'
+import { Badge } from '@/components/ui/badge.tsx'
+import { cn } from '@/lib/utils.ts'
+import { Trash } from 'lucide-react'
+import { Button } from '@/components/ui/button.tsx'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip.tsx'
+
+interface NoteHistoryProps {
+ onSelect: (taskId: string) => void
+ selectedId: string | null
+}
+
+const NoteHistory: FC = ({ onSelect, selectedId }) => {
+ const tasks = useTaskStore(state => state.tasks)
+ const removeTask = useTaskStore(state => state.removeTask)
+
+ if (tasks.length === 0) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+ {tasks.map(task => (
+
onSelect(task.id)}
+ >
+ {/* 封面图 */}
+

+
+ {/* 标题 + 状态 */}
+
+
+
+
+
+
+ {task.audioMeta.title || '未命名笔记'}
+
+
+
+ {task.audioMeta.title || '未命名笔记'}
+
+
+
+
+ {task.status === 'SUCCESS' && 已完成}
+ {task.status === 'PENDING' && 等待中}
+ {task.status === 'FAILED' && 失败}
+
+
+
+ {/* 删除按钮 */}
+
+
+
+
+
+
+ 删除
+
+
+
+
+ ))}
+
+
+ )
+}
+
+export default NoteHistory
diff --git a/BillNote_frontend/src/pages/Index.tsx b/BillNote_frontend/src/pages/Index.tsx
new file mode 100644
index 0000000..92f6090
--- /dev/null
+++ b/BillNote_frontend/src/pages/Index.tsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'react-router-dom'
+
+const Index = () => {
+ return (
+ <>
+
+ >
+ )
+}
+export default Index
diff --git a/BillNote_frontend/src/pages/NotFoundPage/index.tsx b/BillNote_frontend/src/pages/NotFoundPage/index.tsx
new file mode 100644
index 0000000..a3c4930
--- /dev/null
+++ b/BillNote_frontend/src/pages/NotFoundPage/index.tsx
@@ -0,0 +1,25 @@
+// src/pages/NotFoundPage.tsx
+import NotFound from '@/components/Lottie/404.tsx'
+import { Button } from '@/components/ui/button.tsx'
+import { useNavigate } from 'react-router-dom'
+
+const NotFoundPage = () => {
+ const navigate = useNavigate()
+
+ return (
+
+
+
你好像走丢了哦!~~
+
请检查你的网址是否正确,或者点击下面的按钮返回首页。
+
+
+
+
+
+
+ )
+}
+
+export default NotFoundPage
diff --git a/BillNote_frontend/src/pages/SettingPage/Menu.tsx b/BillNote_frontend/src/pages/SettingPage/Menu.tsx
new file mode 100644
index 0000000..85b5b6c
--- /dev/null
+++ b/BillNote_frontend/src/pages/SettingPage/Menu.tsx
@@ -0,0 +1,48 @@
+import { BotMessageSquare, Captions, HardDriveDownload, Wrench } from 'lucide-react'
+import MenuBar, { IMenuProps } from '@/pages/SettingPage/components/menuBar.tsx'
+
+const Menu = () => {
+ const menuList: IMenuProps[] = [
+ {
+ id: 'model',
+ name: 'AI 模型设置',
+ icon: ,
+ path: '/settings/model',
+ },
+ {
+ id: ' transcriber',
+ name: '音频转译配置',
+ icon: ,
+ path: '/settings/transcriber',
+ },
+ //下载配置
+ {
+ id: 'download',
+ name: '下载配置',
+ icon: ,
+ path: '/settings/download',
+ },
+ //其他配置
+ {
+ id: 'other',
+ name: '其他配置',
+ icon: ,
+ path: '/settings/other',
+ },
+ ]
+ return (
+
+
+
+ {menuList &&
+ menuList.map(item => {
+ return
+ })}
+
+
+ )
+}
+export default Menu
diff --git a/BillNote_frontend/src/pages/SettingPage/Model.tsx b/BillNote_frontend/src/pages/SettingPage/Model.tsx
new file mode 100644
index 0000000..775c1ff
--- /dev/null
+++ b/BillNote_frontend/src/pages/SettingPage/Model.tsx
@@ -0,0 +1,16 @@
+import Provider from '@/components/Form/modelForm/Provider.tsx'
+import { Outlet } from 'react-router-dom'
+
+const Model = () => {
+ return (
+
+ )
+}
+export default Model
diff --git a/BillNote_frontend/src/pages/SettingPage/components/index.module.css b/BillNote_frontend/src/pages/SettingPage/components/index.module.css
new file mode 100644
index 0000000..4d85b86
--- /dev/null
+++ b/BillNote_frontend/src/pages/SettingPage/components/index.module.css
@@ -0,0 +1,7 @@
+.menuBar {
+ cursor: pointer;
+ transition: all 0.2s ease-in-out;
+}
+.menuBar:hover {
+ background-color: #f7f7f7;
+}
diff --git a/BillNote_frontend/src/pages/SettingPage/components/menuBar.tsx b/BillNote_frontend/src/pages/SettingPage/components/menuBar.tsx
new file mode 100644
index 0000000..a49470b
--- /dev/null
+++ b/BillNote_frontend/src/pages/SettingPage/components/menuBar.tsx
@@ -0,0 +1,37 @@
+import styles from './index.module.css'
+import { FC, JSX } from 'react'
+import { Link, useLocation } from 'react-router-dom'
+
+export interface IMenuProps {
+ id: string
+ name: string
+ icon: JSX.Element
+ path: string
+}
+
+interface IMenuItem {
+ menuItem: IMenuProps
+}
+
+const MenuBar: FC = ({ menuItem }) => {
+ const location = useLocation()
+ const isActive = location.pathname.startsWith(menuItem.path + '/')
+ || location.pathname === menuItem.path
+
+ return (
+
+
+
{menuItem.icon}
+
{menuItem.name}
+
+
+ )
+}
+
+export default MenuBar
diff --git a/BillNote_frontend/src/pages/SettingPage/index.tsx b/BillNote_frontend/src/pages/SettingPage/index.tsx
new file mode 100644
index 0000000..15d65c9
--- /dev/null
+++ b/BillNote_frontend/src/pages/SettingPage/index.tsx
@@ -0,0 +1,17 @@
+import SettingLayout from '@/layouts/SettingLayout.tsx'
+import Menu from '@/pages/SettingPage/Menu'
+import { useProviderStore } from '@/store/providerStore'
+import { useEffect } from 'react'
+
+const SettingPage = () => {
+ const fetchProviderList = useProviderStore(state => state.fetchProviderList)
+ useEffect(() => {
+ fetchProviderList()
+ }, [])
+ return (
+
+ } />
+
+ )
+}
+export default SettingPage
diff --git a/BillNote_frontend/src/pages/components/MarkdownViewer.tsx b/BillNote_frontend/src/pages/components/MarkdownViewer.tsx
deleted file mode 100644
index 99803d8..0000000
--- a/BillNote_frontend/src/pages/components/MarkdownViewer.tsx
+++ /dev/null
@@ -1,168 +0,0 @@
-import { useState } from "react"
-import ReactMarkdown from "react-markdown"
-import { Button } from "@/components/ui/button"
-import { Copy, Download, FileText,ArrowRight } from "lucide-react"
-import { toast } from "sonner" // 你可以换成自己的通知组件
-import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
-import { solarizedlight as codeStyle } from 'react-syntax-highlighter/dist/cjs/styles/prism'
-import 'github-markdown-css/github-markdown-light.css'
-import {FC} from 'react'
-import Loading from "@/components/Lottie/Loading.tsx";
-import Idle from "@/components/Lottie/Idle.tsx";
-import {useTaskStore} from "@/store/taskStore";
-interface MarkdownViewerProps {
- content: string
- status: 'idle' | 'loading' | 'success'
-}
-
-const MarkdownViewer: FC = ({ content, status }) => {
- const [copied, setCopied] = useState(false)
- const getCurrentTask =useTaskStore.getState().getCurrentTask
- const handleCopy = async () => {
- try {
- await navigator.clipboard.writeText(content)
- setCopied(true)
- toast.success("已复制到剪贴板")
- setTimeout(() => setCopied(false), 2000)
- } catch (e) {
- toast.error(`复制失败${e}`)
- toast.error("复制失败",e)
- }
- }
-
- const handleDownload = () => {
- const currentTask=getCurrentTask()
- const currentTaskName=currentTask?.audioMeta.title
- const blob = new Blob([content], { type: "text/markdown;charset=utf-8" })
- const link = document.createElement("a")
- link.href = URL.createObjectURL(blob)
- link.download = `${currentTaskName}.md`
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- }
- if (status === 'loading') {
- return (
-
-
-
-
正在生成笔记,请稍候…
-
这可能需要几秒钟时间,取决于视频长度
-
-
-
- )
- }
- else if (status === 'idle'){
- return (
-
-
-
-
-
-
输入视频链接并点击“生成笔记”
-
支持哔哩哔哩、YouTube 等视频平台
-
-
-
- )
- }
-
- return (
-
- {/* 顶部操作栏 */}
-
-
-
- 笔记内容
-
-
-
-
-
-
-
- {/* 滚动容器 */}
-
-
- {
- content && content!='loading' || content!='empty'?(
-
-
- {codeContent}
-
-
-
- )
- }
-
- return (
-
- {children}
-
- )
- }
- }}
- >
- {content}
-
- ):(
-
-
-
-
输入视频链接并点击"生成笔记"按钮
-
支持哔哩哔哩、YouTube等视频网站
-
-
- )
- }
-
- {/**/}
- {/* {content ? (*/}
- {/* */}
- {/* ) : (*/}
- {/* <>*/}
- {/*
*/}
- {/*
输入视频链接并点击"生成笔记"按钮
*/}
- {/*
支持哔哩哔哩、YouTube、腾讯视频和爱奇艺
*/}
- {/* >*/}
- {/* )}*/}
- {/*
*/}
-