Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
525 changes: 525 additions & 0 deletions BillNote_frontend/src/components/FavoriteBatchDialog.tsx

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions BillNote_frontend/src/layouts/HomeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
onCollapse={() => setIsMiddleCollapsed(true)}
onExpand={() => setIsMiddleCollapsed(false)}
>
<aside className="flex h-full flex-col overflow-hidden border-r border-neutral-200 bg-white">
<aside className="flex h-full min-w-0 flex-col overflow-hidden border-r border-neutral-200 bg-white">
<header className="flex h-10 shrink-0 items-center justify-between border-b border-neutral-100 px-3">
<span className="text-sm font-medium text-gray-600">生成历史</span>
<TooltipProvider>
Expand All @@ -135,8 +135,8 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
</Tooltip>
</TooltipProvider>
</header>
<ScrollArea className="flex-1 overflow-auto">
<div>{History}</div>
<ScrollArea className="min-w-0 flex-1 overflow-auto">
<div className="min-w-0 w-full">{History}</div>
</ScrollArea>
</aside>
</ResizablePanel>
Expand Down
6 changes: 3 additions & 3 deletions BillNote_frontend/src/pages/HomePage/components/History.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import NoteHistory from '@/pages/HomePage/components/NoteHistory.tsx'
import { useTaskStore } from '@/store/taskStore'
import { Info, Clock, Loader2 } from 'lucide-react'
import { Clock } from 'lucide-react'
import { ScrollArea } from '@/components/ui/scroll-area.tsx'
const History = () => {
const currentTaskId = useTaskStore(state => state.currentTaskId)
const setCurrentTask = useTaskStore(state => state.setCurrentTask)
return (
<>
<div className={'flex h-full w-full flex-col gap-4 px-2.5 py-1.5'}>
<div className={'flex h-full w-full min-w-0 flex-col gap-4 px-2.5 py-1.5'}>
{/*生成历史 */}
<div className="my-4 flex h-[40px] items-center gap-2">
<Clock className="h-4 w-4 text-neutral-500" />
<h2 className="text-base font-medium text-neutral-900">生成历史</h2>
</div>
<ScrollArea className="w-full sm:h-[480px] md:h-[720px] lg:h-[92%]">
<ScrollArea className="w-full min-w-0 sm:h-[480px] md:h-[720px] lg:h-[92%]">
{/*<div className="w-full flex-1 overflow-y-auto">*/}
<NoteHistory onSelect={setCurrentTask} selectedId={currentTaskId} />
{/*</div>*/}
Expand Down
13 changes: 11 additions & 2 deletions BillNote_frontend/src/pages/HomePage/components/MarkdownHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ interface VersionNote {
interface NoteHeaderProps {
currentTask?: {
markdown: VersionNote[] | string
formData?: {
style?: string
}
}
isMultiVersion: boolean
currentVerId: string
Expand Down Expand Up @@ -65,7 +68,13 @@ export function MarkdownHeader({
setCopied(true)
}

const styleName = noteStyles.find(v => v.value === style)?.label || style
const versionStyle = Array.isArray(currentTask?.markdown)
? currentTask.markdown.find(version => version.ver_id === currentVerId)?.style
: undefined
const styleValue = versionStyle || currentTask?.formData?.style || style
const styleName = noteStyles.find(v => v.value === styleValue || v.label === styleValue)?.label
|| styleValue
|| '风格未记录'

const reversedMarkdown: VersionNote[] = Array.isArray(currentTask?.markdown)
? [...currentTask!.markdown].reverse()
Expand Down Expand Up @@ -118,7 +127,7 @@ export function MarkdownHeader({
{modelName}
</Badge>
<Badge variant="secondary" className="bg-cyan-100 text-cyan-700 hover:bg-cyan-200">
{styleName}
笔记风格:{styleName}
</Badge>

{createAt && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ const MarkdownViewer: FC<MarkdownViewerProps> = memo(({ status }) => {
const [currentVerId, setCurrentVerId] = useState<string>('')
const [selectedContent, setSelectedContent] = useState<string>('')
const [modelName, setModelName] = useState<string>('')
const [style, setStyle] = useState<string>('')
const [createTime, setCreateTime] = useState<string>('')
// 确保baseURL没有尾部斜杠
const baseURL = (String(import.meta.env.VITE_API_BASE_URL || '').replace('/api','') || '').replace(/\/$/, '')
Expand All @@ -327,6 +326,10 @@ const MarkdownViewer: FC<MarkdownViewerProps> = memo(({ status }) => {
const taskStatus = currentTask?.status || 'PENDING'
const retryTask = useTaskStore.getState().retryTask
const isMultiVersion = Array.isArray(currentTask?.markdown)
const selectedVersionStyle = currentTask && Array.isArray(currentTask.markdown)
? currentTask.markdown.find(version => version.ver_id === currentVerId)?.style
: undefined
const noteStyle = selectedVersionStyle || currentTask?.formData.style || ''
const [showTranscribe, setShowTranscribe] = useState(false)
const [showChat, setShowChat] = useState<false | 'half' | 'full'>(false)
const [viewMode, setViewMode] = useState<'map' | 'preview'>('preview')
Expand All @@ -341,8 +344,7 @@ const MarkdownViewer: FC<MarkdownViewerProps> = memo(({ status }) => {

if (!isMultiVersion) {
setCurrentVerId('') // 清空旧版本 ID
setModelName(currentTask.formData.model_name)
setStyle(currentTask.formData.style)
setModelName(currentTask.formData.model_name || '')
setCreateTime(currentTask.createdAt)
setSelectedContent(currentTask?.markdown)
} else {
Expand All @@ -354,18 +356,17 @@ const MarkdownViewer: FC<MarkdownViewerProps> = memo(({ status }) => {
setCurrentVerId(latestVersion.ver_id)
}
}
}, [currentTask?.id, taskStatus])
}, [currentTask, isMultiVersion, taskStatus])
useEffect(() => {
if (!currentTask || !isMultiVersion) return

const currentVer = currentTask.markdown.find(v => v.ver_id === currentVerId)
if (currentVer) {
setModelName(currentVer.model_name)
setStyle(currentVer.style)
setModelName(currentVer.model_name || currentTask.formData.model_name || '')
setCreateTime(currentVer.created_at || '')
setSelectedContent(currentVer.content)
}
}, [currentVerId, currentTask?.id])
}, [currentVerId, currentTask, isMultiVersion])
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(selectedContent)
Expand Down Expand Up @@ -464,7 +465,7 @@ const MarkdownViewer: FC<MarkdownViewerProps> = memo(({ status }) => {
currentVerId={currentVerId}
setCurrentVerId={setCurrentVerId}
modelName={modelName}
style={style}
style={noteStyle}
noteStyles={noteStyles}
onCopy={handleCopy}
onDownload={handleDownload}
Expand Down
65 changes: 57 additions & 8 deletions BillNote_frontend/src/pages/HomePage/components/NoteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { useForm, useWatch } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

import { Info, Loader2, Plus } from 'lucide-react'
import { Info, ListVideo, Loader2, Plus } from 'lucide-react'
import { Alert, AlertDescription } from '@/components/ui/alert.tsx'
import { generateNote } from '@/services/note.ts'
import { generateNote, type GenerateNotePayload } from '@/services/note.ts'
import { uploadFile } from '@/services/upload.ts'
import { useTaskStore } from '@/store/taskStore'
import { useModelStore } from '@/store/modelStore'
Expand All @@ -40,6 +40,7 @@ import { noteStyles, noteFormats, videoPlatforms } from '@/constant/note.ts'
import { fetchModels } from '@/services/model.ts'
import { useNavigate } from 'react-router-dom'
import toast from 'react-hot-toast'
import FavoriteBatchDialog from '@/components/FavoriteBatchDialog'

/* -------------------- 校验 Schema -------------------- */
/** 用户粘贴的链接常缺协议头(如 bilibili.com/...),无任何 scheme 时自动补 https:// */
Expand Down Expand Up @@ -135,6 +136,7 @@ const NoteForm = () => {
const navigate = useNavigate();
const [isUploading, setIsUploading] = useState(false)
const [uploadSuccess, setUploadSuccess] = useState(false)
const [favoriteBatchOpen, setFavoriteBatchOpen] = useState(false)
/* ---- 全局状态 ---- */
const { addPendingTask, currentTaskId, setCurrentTask, getCurrentTask, retryTask } =
useTaskStore()
Expand Down Expand Up @@ -163,6 +165,28 @@ const NoteForm = () => {
const goModelAdd = () => {
navigate("/settings/model");
};

const getFavoriteGenerationConfig = () => {
const values = form.getValues()
const selectedModel = modelList.find(model => model.model_name === values.model_name)
if (!selectedModel) {
toast.error('请先选择有效的模型')
return null
}
return {
quality: values.quality,
screenshot: values.screenshot ?? false,
link: values.link ?? false,
model_name: values.model_name,
provider_id: selectedModel.provider_id,
format: values.format ?? [],
style: values.style || 'minimal',
extras: values.extras || '',
video_understanding: values.video_understanding ?? false,
video_interval: values.video_interval ?? 6,
grid_size: values.grid_size ?? [2, 2],
}
}
/* ---- 副作用 ---- */
useEffect(() => {
loadEnabledModels()
Expand Down Expand Up @@ -222,12 +246,19 @@ const NoteForm = () => {

const onSubmit = async (values: NoteFormValues) => {
console.log('Not even go here')
const payload: NoteFormValues = {
const selectedModel = modelList.find(model => model.model_name === values.model_name)
if (!selectedModel?.provider_id) {
toast.error('请先选择有效的模型提供者')
return
}
const payload: GenerateNotePayload = {
...values,
video_url:
values.platform === 'local' ? values.video_url : withScheme(values.video_url || ''),
provider_id: modelList.find(m => m.model_name === values.model_name)!.provider_id,
values.platform === 'local' ? values.video_url || '' : withScheme(values.video_url || ''),
provider_id: selectedModel.provider_id,
task_id: currentTaskId || '',
format: values.format ?? [],
grid_size: values.grid_size ?? [2, 2],
}
if (currentTaskId) {
retryTask(currentTaskId, payload)
Expand All @@ -238,11 +269,12 @@ const NoteForm = () => {
try {
const data = await generateNote(payload)
addPendingTask(data.task_id, values.platform, payload)
} catch (e: any) {
} catch (e: unknown) {
const failure = e as { data?: { reason?: string; downloading?: boolean } }
// 就绪门禁:本地转写模型还没下载好。后端返回 reason='transcriber_model_not_ready',
// 引导用户去「设置 → 音频转写配置」下载,而不是留一个静默失败的任务。
if (e?.data?.reason === 'transcriber_model_not_ready') {
const downloading = e?.data?.downloading
if (failure?.data?.reason === 'transcriber_model_not_ready') {
const downloading = failure.data.downloading
toast.error(
downloading
? '转写模型正在下载中,请稍候再提交'
Expand Down Expand Up @@ -351,6 +383,18 @@ const NoteForm = () => {
/>
</div>

{platform === 'bilibili' && (
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => setFavoriteBatchOpen(true)}
>
<ListVideo className="h-4 w-4" />
从 B 站收藏夹批量生成
</Button>
)}

<FormField
control={form.control}
name="video_url"
Expand Down Expand Up @@ -576,6 +620,11 @@ const NoteForm = () => {
)}
/>
</form>
<FavoriteBatchDialog
open={favoriteBatchOpen}
onOpenChange={setFavoriteBatchOpen}
getGenerationConfig={getFavoriteGenerationConfig}
/>
</Form>
</div>
)
Expand Down
Loading