auto-save 2026-04-13 18:59 (+1)

This commit is contained in:
2026-04-13 19:00:17 +08:00
parent c35c1852af
commit 4bdc14f96c
88 changed files with 11213 additions and 0 deletions

146
web/app/upload/page.tsx Normal file
View File

@@ -0,0 +1,146 @@
'use client'
import { useState, useRef } from 'react'
import { UploadCloud, FileAudio, X } from 'lucide-react'
import Link from 'next/link'
import { AppShell } from '@/components/app-shell'
import { cn } from '@/lib/utils'
export default function UploadPage() {
const [file, setFile] = useState<File | null>(null)
const [title, setTitle] = useState('')
const [participants, setParticipants] = useState('')
const [dragOver, setDragOver] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const pickFile = (f: File | null | undefined) => {
if (f) setFile(f)
}
const humanSize = (bytes: number) => {
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB'
return (bytes / 1024 / 1024).toFixed(1) + ' MB'
}
return (
<AppShell>
<div className="mx-auto max-w-2xl px-5 py-8 md:px-10 md:py-10">
<header className="mb-8">
<h1 className="text-[26px] font-semibold tracking-tight"></h1>
<p className="mt-1 text-[14px] text-muted-foreground">
</p>
</header>
{/* Dropzone */}
{!file ? (
<div
onDragOver={(e) => {
e.preventDefault()
setDragOver(true)
}}
onDragLeave={() => setDragOver(false)}
onDrop={(e) => {
e.preventDefault()
setDragOver(false)
pickFile(e.dataTransfer.files?.[0])
}}
onClick={() => inputRef.current?.click()}
className={cn(
'flex flex-col items-center justify-center rounded-2xl border-2 border-dashed px-6 py-16 transition-all cursor-pointer',
dragOver
? 'border-primary bg-primary-subtle'
: 'border-border bg-surface-elevated hover:border-primary/50 hover:bg-primary-subtle/50',
)}
>
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-primary-subtle text-primary">
<UploadCloud className="h-7 w-7" />
</div>
<h3 className="mt-5 text-[17px] font-semibold"></h3>
<p className="mt-1 text-[13px] text-muted-foreground"></p>
<p className="mt-4 text-[12px] text-muted-foreground">
m4a · mp3 · wav · mp4 · opus · 500 MB
</p>
<input
ref={inputRef}
type="file"
accept="audio/*,video/mp4"
className="hidden"
onChange={(e) => pickFile(e.target.files?.[0])}
/>
</div>
) : (
<div className="rounded-2xl border border-border bg-surface-elevated p-5">
<div className="flex items-start gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-subtle text-primary shrink-0">
<FileAudio className="h-6 w-6" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<h3 className="text-[15px] font-semibold truncate">{file.name}</h3>
<button
onClick={() => setFile(null)}
className="rounded-lg p-1.5 text-muted-foreground hover:bg-surface-muted"
>
<X className="h-4 w-4" />
</button>
</div>
<p className="mt-1 text-[13px] text-muted-foreground">{humanSize(file.size)}</p>
</div>
</div>
</div>
)}
{/* Form */}
<div className="mt-6 space-y-5">
<div>
<label className="block text-[13px] font-medium mb-2"></label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="例:与客户对齐定价"
className="w-full rounded-xl border border-input bg-surface-elevated px-4 py-3 text-[14px] placeholder:text-muted-foreground/60 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
/>
</div>
<div>
<label className="block text-[13px] font-medium mb-2">
<span className="ml-2 text-[11px] font-normal text-muted-foreground">
</span>
</label>
<input
type="text"
value={participants}
onChange={(e) => setParticipants(e.target.value)}
placeholder="张三, 李四, 客户王总"
className="w-full rounded-xl border border-input bg-surface-elevated px-4 py-3 text-[14px] placeholder:text-muted-foreground/60 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
/>
</div>
</div>
{/* Actions */}
<div className="mt-8 flex items-center justify-end gap-3">
<Link
href="/"
className="rounded-full px-5 py-2.5 text-[14px] font-medium text-muted-foreground hover:bg-surface-muted"
>
</Link>
<button
disabled={!file}
className="rounded-full bg-primary px-6 py-2.5 text-[14px] font-medium text-primary-foreground transition-colors hover:bg-primary-light disabled:opacity-40 disabled:cursor-not-allowed"
>
</button>
</div>
<p className="mt-8 text-center text-[12px] text-muted-foreground">
Groq Whisper · Claude / /
</p>
</div>
</AppShell>
)
}