feat: Brief附件/项目平台/规则AI解析/消息中心修复 + 项目创建通知
- Brief 支持代理商附件上传 (迁移 007) - 项目新增 platform 字段 (迁移 008),前端创建/展示平台信息 - 修复 AI 规则解析:处理中文引号导致 JSON 解析失败的问题 - 修复消息中心崩溃:补全后端消息类型映射 + fallback 保护 - 项目创建时自动发送消息通知 - .gitignore 排除 backend/data/ 数据库文件 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
58aed5f201
commit
4c9b2f1263
@@ -16,7 +16,6 @@ import { Modal } from '@/components/ui/Modal'
|
||||
import { api } from '@/lib/api'
|
||||
import { USE_MOCK } from '@/contexts/AuthContext'
|
||||
import { useSSE } from '@/contexts/SSEContext'
|
||||
import { useOSSUpload } from '@/hooks/useOSSUpload'
|
||||
import type { TaskResponse, AIReviewResult } from '@/types/task'
|
||||
import type { BriefResponse } from '@/types/brief'
|
||||
|
||||
@@ -217,64 +216,109 @@ function AgencyBriefSection({ toast, briefData }: { toast: ReturnType<typeof use
|
||||
|
||||
function UploadSection({ taskId, onUploaded }: { taskId: string; onUploaded: () => void }) {
|
||||
const [file, setFile] = useState<File | null>(null)
|
||||
const { upload, isUploading, progress } = useOSSUpload('script')
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [progress, setProgress] = useState(0)
|
||||
const [uploadError, setUploadError] = useState<string | null>(null)
|
||||
const toast = useToast()
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const selectedFile = e.target.files?.[0]
|
||||
if (selectedFile) setFile(selectedFile)
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile)
|
||||
setUploadError(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!file) return
|
||||
setIsUploading(true)
|
||||
setProgress(0)
|
||||
setUploadError(null)
|
||||
try {
|
||||
const result = await upload(file)
|
||||
if (!USE_MOCK) {
|
||||
if (USE_MOCK) {
|
||||
for (let i = 0; i <= 100; i += 20) {
|
||||
await new Promise(r => setTimeout(r, 400))
|
||||
setProgress(i)
|
||||
}
|
||||
toast.success('脚本已提交,等待 AI 审核')
|
||||
onUploaded()
|
||||
} else {
|
||||
const result = await api.proxyUpload(file, 'script', (pct) => {
|
||||
setProgress(Math.min(90, Math.round(pct * 0.9)))
|
||||
})
|
||||
setProgress(95)
|
||||
await api.uploadTaskScript(taskId, { file_url: result.url, file_name: result.file_name })
|
||||
setProgress(100)
|
||||
toast.success('脚本已提交,等待 AI 审核')
|
||||
onUploaded()
|
||||
}
|
||||
toast.success('脚本已提交,等待 AI 审核')
|
||||
onUploaded()
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : '上传失败')
|
||||
const msg = err instanceof Error ? err.message : '上传失败'
|
||||
setUploadError(msg)
|
||||
toast.error(msg)
|
||||
} finally {
|
||||
setIsUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const formatSize = (bytes: number) => {
|
||||
if (bytes < 1024) return bytes + 'B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + 'MB'
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle className="flex items-center gap-2"><Upload size={18} className="text-accent-indigo" />上传脚本</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="border-2 border-dashed border-border-subtle rounded-lg p-8 text-center hover:border-accent-indigo/50 transition-colors">
|
||||
{file ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<FileText size={24} className="text-accent-indigo" />
|
||||
<span className="text-text-primary">{file.name}</span>
|
||||
{!file ? (
|
||||
<label className="border-2 border-dashed border-border-subtle rounded-lg p-8 text-center hover:border-accent-indigo/50 transition-colors cursor-pointer block">
|
||||
<Upload size={32} className="mx-auto text-text-tertiary mb-3" />
|
||||
<p className="text-text-secondary mb-1">点击上传脚本文件</p>
|
||||
<p className="text-xs text-text-tertiary">支持 Word、PDF、TXT 格式</p>
|
||||
<input type="file" accept=".doc,.docx,.pdf,.txt" onChange={handleFileChange} className="hidden" />
|
||||
</label>
|
||||
) : (
|
||||
<div className="border border-border-subtle rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-2.5 bg-bg-elevated border-b border-border-subtle">
|
||||
<span className="text-xs font-medium text-text-secondary">已选文件</span>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{isUploading ? (
|
||||
<Loader2 size={16} className="animate-spin text-accent-indigo flex-shrink-0" />
|
||||
) : uploadError ? (
|
||||
<AlertTriangle size={16} className="text-accent-coral flex-shrink-0" />
|
||||
) : (
|
||||
<CheckCircle size={16} className="text-accent-green flex-shrink-0" />
|
||||
)}
|
||||
<FileText size={14} className="text-accent-indigo flex-shrink-0" />
|
||||
<span className="flex-1 text-sm text-text-primary truncate">{file.name}</span>
|
||||
<span className="text-xs text-text-tertiary">{formatSize(file.size)}</span>
|
||||
{!isUploading && (
|
||||
<button type="button" onClick={() => setFile(null)} className="p-1 hover:bg-bg-elevated rounded-full">
|
||||
<XCircle size={16} className="text-text-tertiary" />
|
||||
<button type="button" onClick={() => { setFile(null); setUploadError(null) }} className="p-1 hover:bg-bg-elevated rounded">
|
||||
<XCircle size={14} className="text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isUploading && (
|
||||
<div className="w-full max-w-xs mx-auto">
|
||||
<div className="h-2 bg-bg-elevated rounded-full overflow-hidden mb-2">
|
||||
<div className="h-full bg-accent-indigo transition-all" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
<p className="text-sm text-text-tertiary">上传中 {progress}%</p>
|
||||
<div className="mt-2 ml-[30px] h-2 bg-bg-page rounded-full overflow-hidden">
|
||||
<div className="h-full bg-accent-indigo rounded-full transition-all duration-300" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
)}
|
||||
{isUploading && (
|
||||
<p className="mt-1 ml-[30px] text-xs text-text-tertiary">上传中 {progress}%</p>
|
||||
)}
|
||||
{uploadError && (
|
||||
<p className="mt-1 ml-[30px] text-xs text-accent-coral">{uploadError}</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<label className="cursor-pointer">
|
||||
<Upload size={32} className="mx-auto text-text-tertiary mb-3" />
|
||||
<p className="text-text-secondary mb-1">点击或拖拽上传脚本文件</p>
|
||||
<p className="text-xs text-text-tertiary">支持 Word、PDF、TXT 格式</p>
|
||||
<input type="file" accept=".doc,.docx,.pdf,.txt" onChange={handleFileChange} className="hidden" />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={handleSubmit} disabled={!file || isUploading} fullWidth>
|
||||
{isUploading ? '上传中...' : '提交脚本'}
|
||||
{isUploading ? (
|
||||
<><Loader2 size={16} className="animate-spin" />上传中 {progress}%</>
|
||||
) : '提交脚本'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user