mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-05-10 17:43:40 +08:00
### 性能优化 - 后端任务执行从串行锁改为 ThreadPoolExecutor 并发执行(默认3线程) - 添加 GZipMiddleware 响应压缩 + Nginx gzip 配置 - 数据库连接池参数优化(pool_size=10, max_overflow=20) - 视频帧提取并行化(ThreadPoolExecutor) - LLM 重试配置缓存到实例,避免每次请求读 env var - 前端路由级代码拆分(React.lazy + Suspense) - Vite manualChunks 拆分 markdown/markmap/vendor - MarkdownViewer 用 React.memo + useMemo 减少不必要渲染 - NoteHistory Fuse.js 实例 useMemo 缓存 - useTaskPolling 无待处理任务时跳过轮询 - 移除 antd 依赖(NoteForm Alert、modelForm Tag),改用 shadcn/ui ### 前端转写器配置(新功能) - 新增 TranscriberConfigManager(JSON 文件存储,替代环境变量) - 新增 GET/POST /transcriber_config API 端点 - 新增 GET /transcriber_models_status 模型下载状态查询 - 新增 POST /transcriber_download 后台模型下载触发 - 前端转写器设置页面:引擎选择、模型大小选择、模型下载管理 - deploy_status 端点同步从配置文件读取 ### Bug 修复 - 修复任务进行中切换页面后进度丢失:Home.tsx status 派生逻辑补全中间状态 - 修复 MLX Whisper 静默回退 fast-whisper:移除环境变量门控,macOS 下自动尝试导入 - MLX Whisper 不可用时抛出 RuntimeError 而非静默回退 - 前端展示 MLX Whisper 可用性状态,不可用时禁用保存 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
187 lines
6.5 KiB
TypeScript
187 lines
6.5 KiB
TypeScript
import { useTaskStore } from '@/store/taskStore'
|
|
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 PinyinMatch from 'pinyin-match'
|
|
import Fuse from 'fuse.js'
|
|
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip.tsx'
|
|
import LazyImage from "@/components/LazyImage.tsx";
|
|
import {FC, useState, useEffect, useMemo} from 'react'
|
|
|
|
interface NoteHistoryProps {
|
|
onSelect: (taskId: string) => void
|
|
selectedId: string | null
|
|
}
|
|
|
|
const NoteHistory: FC<NoteHistoryProps> = ({ onSelect, selectedId }) => {
|
|
const tasks = useTaskStore(state => state.tasks)
|
|
const removeTask = useTaskStore(state => state.removeTask)
|
|
// 确保baseURL没有尾部斜杠
|
|
const baseURL = (String(import.meta.env.VITE_API_BASE_URL || 'api')).replace(/\/$/, '')
|
|
const [rawSearch, setRawSearch] = useState('')
|
|
const [search, setSearch] = useState('')
|
|
const fuse = useMemo(() => new Fuse(tasks, {
|
|
keys: ['audioMeta.title'],
|
|
threshold: 0.4 // 匹配精度(越低越严格)
|
|
}), [tasks])
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
if (rawSearch === '') return
|
|
setSearch(rawSearch)
|
|
}, 300) // 300ms 防抖
|
|
|
|
return () => clearTimeout(timer)
|
|
}, [rawSearch])
|
|
const filteredTasks = search.trim()
|
|
? fuse.search(search).map(result => result.item)
|
|
: tasks
|
|
if (filteredTasks.length === 0) {
|
|
return (
|
|
<>
|
|
<div className="mb-2">
|
|
<input
|
|
type="text"
|
|
placeholder="搜索笔记标题..."
|
|
className="w-full rounded border border-neutral-300 px-3 py-1 text-sm outline-none focus:border-primary"
|
|
value={search}
|
|
onChange={e => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="rounded-md border border-neutral-200 bg-neutral-50 py-6 text-center">
|
|
<p className="text-sm text-neutral-500">暂无记录</p>
|
|
</div>
|
|
</>
|
|
|
|
)
|
|
}
|
|
|
|
|
|
return (
|
|
<>
|
|
<div className="mb-2">
|
|
<input
|
|
type="text"
|
|
placeholder="搜索笔记标题..."
|
|
className="w-full rounded border border-neutral-300 px-3 py-1 text-sm outline-none focus:border-primary"
|
|
value={search}
|
|
onChange={e => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2 overflow-hidden">
|
|
{filteredTasks.map(task => (
|
|
<div
|
|
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
|
|
className={cn('flex items-center gap-4')}
|
|
>
|
|
{/* 封面图 */}
|
|
{task.platform === 'local' ? (
|
|
<img
|
|
src={
|
|
task.audioMeta.cover_url ? `${task.audioMeta.cover_url}` : '/placeholder.png'
|
|
}
|
|
alt="封面"
|
|
className="h-10 w-12 rounded-md object-cover"
|
|
/>
|
|
) : (
|
|
<LazyImage
|
|
|
|
src={
|
|
task.audioMeta.cover_url
|
|
? `${baseURL}/image_proxy?url=${encodeURIComponent(task.audioMeta.cover_url)}`
|
|
: '/placeholder.png'
|
|
}
|
|
alt="封面"
|
|
/>
|
|
)}
|
|
|
|
{/* 标题 + 状态 */}
|
|
|
|
<div className="flex w-full items-center justify-between gap-2">
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div className="line-clamp-2 max-w-[180px] flex-1 overflow-hidden text-sm text-ellipsis">
|
|
{task.audioMeta.title || '未命名笔记'}
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>{task.audioMeta.title || '未命名笔记'}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
</div>
|
|
<div className={'mt-2 flex items-center justify-between text-[10px]'}>
|
|
<div className="shrink-0">
|
|
{task.status === 'SUCCESS' && (
|
|
<div className={'bg-primary w-10 rounded p-0.5 text-center text-white'}>
|
|
已完成
|
|
</div>
|
|
)}
|
|
{task.status !== 'SUCCESS' && task.status !== 'FAILED' ? (
|
|
<div className={'w-10 rounded bg-green-500 p-0.5 text-center text-white'}>
|
|
等待中
|
|
</div>
|
|
) : (
|
|
<></>
|
|
)}
|
|
{task.status === 'FAILED' && (
|
|
<div className={'w-10 rounded bg-red-500 p-0.5 text-center text-white'}>失败</div>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
size="small"
|
|
variant="ghost"
|
|
onClick={e => {
|
|
e.stopPropagation()
|
|
removeTask(task.id)
|
|
}}
|
|
className="shrink-0"
|
|
>
|
|
<Trash className="text-muted-foreground h-4 w-4" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>删除</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
{/*<div className="shrink-0">*/}
|
|
{/* {task.status === 'SUCCESS' && <Badge variant="default">已完成</Badge>}*/}
|
|
{/* {task.status !== 'SUCCESS' && task.status === 'FAILED' && (*/}
|
|
{/* <Badge variant="outline">等待中</Badge>*/}
|
|
{/* )}*/}
|
|
{/* {task.status === 'FAILED' && <Badge variant="destructive">失败</Badge>}*/}
|
|
{/*</div>*/}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default NoteHistory
|