Merge pull request #172 from Karasukaigan/master

fix: 修复多个前后端错误与警告
This commit is contained in:
Jianwu Huang
2025-07-02 15:13:35 +08:00
committed by GitHub
8 changed files with 68 additions and 20 deletions

View File

@@ -2,7 +2,7 @@ import './App.css'
import { HomePage } from './pages/HomePage/Home.tsx'
import { useTaskPolling } from '@/hooks/useTaskPolling.ts'
import SettingPage from './pages/SettingPage/index.tsx'
import { BrowserRouter, HashRouter, Navigate, Routes } from 'react-router-dom'
import { BrowserRouter, Navigate, Routes } from 'react-router-dom'
import { Route } from 'react-router-dom'
import Index from '@/pages/Index.tsx'
import NotFoundPage from '@/pages/NotFoundPage'
@@ -43,7 +43,7 @@ function App() {
// 后端已初始化,渲染主应用
return (
<>
<HashRouter>
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />}>
<Route index element={<HomePage />} />
@@ -62,7 +62,7 @@ function App() {
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</HashRouter>
</BrowserRouter>
</>
)
}

View File

@@ -1,10 +1,26 @@
import * as React from 'react'
import { useState, useEffect } from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { CheckIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
function Checkbox({ className, checked, onChange, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
const [isChecked, setIsChecked] = useState(checked || false);
useEffect(() => {
if (checked !== undefined) {
setIsChecked(checked);
}
}, [checked]);
const handleCheckChange = (newChecked: boolean) => {
setIsChecked(newChecked);
if (onChange) {
onChange({} as React.FormEvent<HTMLButtonElement>);
}
};
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
@@ -12,6 +28,8 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className
)}
checked={isChecked}
onCheckedChange={handleCheckChange}
{...props}
>
<CheckboxPrimitive.Indicator

View File

@@ -2,7 +2,7 @@ import * as React from 'react'
import { cn } from '@/lib/utils'
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
function Input({ className, type, value, onChange, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
@@ -13,6 +13,8 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className
)}
value={value ?? ''}
onChange={onChange}
{...props}
/>
)

View File

@@ -24,7 +24,7 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
<div className="flex h-screen flex-col overflow-hidden">
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
{/* 左边表单 */}
<ResizablePanel defaultSize={18} minSize={10} maxSize={35}>
<ResizablePanel defaultSize={23} minSize={10} maxSize={35}>
<aside className="flex h-full flex-col overflow-hidden border-r border-neutral-200 bg-white">
<header className="flex h-16 items-center justify-between px-6">
<div className="flex items-center gap-2">
@@ -68,7 +68,7 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
<ResizableHandle />
{/* 右边预览 */}
<ResizablePanel defaultSize={55} minSize={30}>
<ResizablePanel defaultSize={61} minSize={30}>
<main className="flex h-full flex-col overflow-hidden bg-white p-6">{Preview}</main>
</ResizablePanel>
</ResizablePanelGroup>

View File

@@ -43,7 +43,7 @@ import { useNavigate } from 'react-router-dom'
/* -------------------- 校验 Schema -------------------- */
const formSchema = z
.object({
video_url: z.string(),
video_url: z.string().optional(),
platform: z.string().nonempty('请选择平台'),
quality: z.enum(['fast', 'medium', 'slow']),
screenshot: z.boolean().optional(),
@@ -60,10 +60,10 @@ const formSchema = z
.optional(),
})
.superRefine(({ video_url, platform }, ctx) => {
if (platform === 'local' || platform === 'douyin') {
if (!video_url) {
ctx.addIssue({ code: 'custom', message: '本地视频路径不能为空', path: ['video_url'] })
}
if (platform === 'local' && !video_url) {
ctx.addIssue({ code: 'custom', message: '本地视频路径不能为空', path: ['video_url'] })
} else if (!video_url) {
ctx.addIssue({ code: 'custom', message: '视频链接不能为空', path: ['video_url'] })
} else {
try {
const url = new URL(video_url)
@@ -202,7 +202,7 @@ const NoteForm = () => {
setUploadSuccess(true)
} catch (err) {
console.error('上传失败:', err)
message.error('上传失败,请重试')
// message.error('上传失败,请重试')
} finally {
setIsUploading(false)
}
@@ -220,13 +220,13 @@ const NoteForm = () => {
return
}
message.success('已提交任务')
// message.success('已提交任务')
const data = await generateNote(payload)
addPendingTask(data.task_id, values.platform, payload)
}
const onInvalid = (errors: FieldErrors<NoteFormValues>) => {
console.warn('表单校验失败:', errors)
message.error('请完善所有必填项后再提交')
// message.error('请完善所有必填项后再提交')
}
const handleCreateNew = () => {
// 🔁 这里清空当前任务状态
@@ -297,7 +297,7 @@ const NoteForm = () => {
))}
</SelectContent>
</Select>
<FormMessage />
<FormMessage style={{ display: 'none' }} />
</FormItem>
)}
/>
@@ -314,7 +314,7 @@ const NoteForm = () => {
) : (
<Input disabled={!!editing} placeholder="请输入视频网站链接" {...field} />
)}
<FormMessage />
<FormMessage style={{ display: 'none' }} />
</FormItem>
)}
/>

View File

@@ -77,16 +77,15 @@ const NoteHistory: FC<NoteHistoryProps> = ({ onSelect, selectedId }) => {
<div className="flex flex-col gap-2 overflow-hidden">
{filteredTasks.map(task => (
<div
onClick={() => onSelect(task.id)}
key={task.id}
onClick={() => onSelect(task.id)}
className={cn(
'flex cursor-pointer flex-col rounded-md border border-neutral-200 p-3',
selectedId === task.id && 'border-primary bg-primary-light'
)}
>
<div
key={task.id}
className={cn('flex items-center gap-4')}
>
{/* 封面图 */}
{task.platform === 'local' ? (

View File

@@ -1,5 +1,6 @@
import re
from typing import Optional
import requests
def extract_video_id(url: str, platform: str) -> Optional[str]:
@@ -11,6 +12,12 @@ def extract_video_id(url: str, platform: str) -> Optional[str]:
:return: 提取到的视频 ID 或 None
"""
if platform == "bilibili":
# 如果是短链接,则解析真实链接
if "b23.tv" in url:
resolved_url = resolve_bilibili_short_url(url)
if resolved_url:
url = resolved_url
# 匹配 BV号如 BV1vc411b7Wa
match = re.search(r"BV([0-9A-Za-z]+)", url)
return f"BV{match.group(1)}" if match else None
@@ -26,3 +33,18 @@ def extract_video_id(url: str, platform: str) -> Optional[str]:
return match.group(1) if match else None
return None
def resolve_bilibili_short_url(short_url: str) -> Optional[str]:
"""
解析哔哩哔哩短链接以获取真实视频链接
:param short_url: Bilibili短链接"https://b23.tv/xxxxxx"
:return: 真实的视频链接或None
"""
try:
response = requests.head(short_url, allow_redirects=True)
return response.url
except requests.RequestException as e:
print(f"Error resolving short URL: {e}")
return None

View File

@@ -1,5 +1,6 @@
from pydantic import AnyUrl, validator, BaseModel, field_validator
import re
from urllib.parse import urlparse
SUPPORTED_PLATFORMS = {
"bilibili": r"(https?://)?(www\.)?bilibili\.com/video/[a-zA-Z0-9]+",
@@ -10,6 +11,12 @@ SUPPORTED_PLATFORMS = {
def is_supported_video_url(url: str) -> bool:
parsed = urlparse(url)
# 检查是否为Bilibili的短链接
if parsed.netloc == "b23.tv":
return True
for name, pattern in SUPPORTED_PLATFORMS.items():
if pattern in ["douyin", "kuaishou"]:
if pattern in url: