Initialized repository for project AI scoring application
Co-authored-by: 吳佩庭 <190080258+WuPeiTing0919@users.noreply.github.com>
This commit is contained in:
22
app/client-layout.tsx
Normal file
22
app/client-layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { Analytics } from "@vercel/analytics/next"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
export default function ClientLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
|
||||
<Analytics />
|
||||
</>
|
||||
)
|
||||
}
|
293
app/criteria/page.tsx
Normal file
293
app/criteria/page.tsx
Normal file
@@ -0,0 +1,293 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Sidebar } from "@/components/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Plus, Trash2, Save, RotateCcw, FileText } from "lucide-react"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
|
||||
interface CriteriaItem {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
weight: number
|
||||
maxScore: number
|
||||
}
|
||||
|
||||
const defaultCriteria: CriteriaItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "內容品質",
|
||||
description: "內容的準確性、完整性和專業度",
|
||||
weight: 25,
|
||||
maxScore: 10,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "視覺設計",
|
||||
description: "版面設計、色彩搭配和視覺效果",
|
||||
weight: 20,
|
||||
maxScore: 10,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "邏輯結構",
|
||||
description: "內容組織的邏輯性和條理性",
|
||||
weight: 20,
|
||||
maxScore: 10,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "創新性",
|
||||
description: "創意思維和獨特觀點的展現",
|
||||
weight: 15,
|
||||
maxScore: 10,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "實用性",
|
||||
description: "內容的實際應用價值和可操作性",
|
||||
weight: 20,
|
||||
maxScore: 10,
|
||||
},
|
||||
]
|
||||
|
||||
export default function CriteriaPage() {
|
||||
const [criteria, setCriteria] = useState<CriteriaItem[]>(defaultCriteria)
|
||||
const [templateName, setTemplateName] = useState("預設評分標準")
|
||||
const { toast } = useToast()
|
||||
|
||||
const addCriteria = () => {
|
||||
const newCriteria: CriteriaItem = {
|
||||
id: Date.now().toString(),
|
||||
name: "",
|
||||
description: "",
|
||||
weight: 10,
|
||||
maxScore: 10,
|
||||
}
|
||||
setCriteria([...criteria, newCriteria])
|
||||
}
|
||||
|
||||
const removeCriteria = (id: string) => {
|
||||
setCriteria(criteria.filter((item) => item.id !== id))
|
||||
}
|
||||
|
||||
const updateCriteria = (id: string, field: keyof CriteriaItem, value: string | number) => {
|
||||
setCriteria(criteria.map((item) => (item.id === id ? { ...item, [field]: value } : item)))
|
||||
}
|
||||
|
||||
const updateWeight = (id: string, weight: number[]) => {
|
||||
updateCriteria(id, "weight", weight[0])
|
||||
}
|
||||
|
||||
const totalWeight = criteria.reduce((sum, item) => sum + item.weight, 0)
|
||||
|
||||
const saveCriteria = () => {
|
||||
if (totalWeight !== 100) {
|
||||
toast({
|
||||
title: "權重設定錯誤",
|
||||
description: "所有評分項目的權重總和必須等於 100%",
|
||||
variant: "destructive",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (criteria.some((item) => !item.name.trim())) {
|
||||
toast({
|
||||
title: "設定不完整",
|
||||
description: "請填寫所有評分項目的名稱",
|
||||
variant: "destructive",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 這裡會連接到後端 API 儲存評分標準
|
||||
toast({
|
||||
title: "儲存成功",
|
||||
description: "評分標準已成功儲存",
|
||||
})
|
||||
}
|
||||
|
||||
const resetToDefault = () => {
|
||||
setCriteria(defaultCriteria)
|
||||
setTemplateName("預設評分標準")
|
||||
toast({
|
||||
title: "已重置",
|
||||
description: "評分標準已重置為預設值",
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Sidebar />
|
||||
|
||||
<main className="md:ml-64 p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8 pt-8 md:pt-0">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]">評分標準設定</h1>
|
||||
<p className="text-muted-foreground">自定義評分項目和權重,建立符合您需求的評審標準</p>
|
||||
</div>
|
||||
|
||||
{/* Template Name */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5" />
|
||||
標準模板名稱
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Input
|
||||
value={templateName}
|
||||
onChange={(e) => setTemplateName(e.target.value)}
|
||||
placeholder="輸入評分標準名稱"
|
||||
className="max-w-md"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Weight Summary */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm font-medium">權重總計</span>
|
||||
<Badge variant={totalWeight === 100 ? "default" : "destructive"}>{totalWeight}%</Badge>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all ${totalWeight === 100 ? "bg-primary" : "bg-destructive"}`}
|
||||
style={{ width: `${Math.min(totalWeight, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
{totalWeight !== 100 && <p className="text-sm text-destructive mt-2">權重總和必須等於 100%</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Criteria List */}
|
||||
<div className="space-y-4 mb-6">
|
||||
{criteria.map((item, index) => (
|
||||
<Card key={item.id}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg">評分項目 {index + 1}</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeCriteria(item.id)}
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor={`name-${item.id}`}>項目名稱</Label>
|
||||
<Input
|
||||
id={`name-${item.id}`}
|
||||
value={item.name}
|
||||
onChange={(e) => updateCriteria(item.id, "name", e.target.value)}
|
||||
placeholder="例如:內容品質"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor={`maxScore-${item.id}`}>滿分</Label>
|
||||
<Input
|
||||
id={`maxScore-${item.id}`}
|
||||
type="number"
|
||||
value={item.maxScore}
|
||||
onChange={(e) => updateCriteria(item.id, "maxScore", Number.parseInt(e.target.value) || 10)}
|
||||
min="1"
|
||||
max="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor={`description-${item.id}`}>評分說明</Label>
|
||||
<Textarea
|
||||
id={`description-${item.id}`}
|
||||
value={item.description}
|
||||
onChange={(e) => updateCriteria(item.id, "description", e.target.value)}
|
||||
placeholder="描述此項目的評分標準和要求"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label>權重比例</Label>
|
||||
<Badge variant="outline">{item.weight}%</Badge>
|
||||
</div>
|
||||
<Slider
|
||||
value={[item.weight]}
|
||||
onValueChange={(value) => updateWeight(item.id, value)}
|
||||
max={100}
|
||||
min={0}
|
||||
step={5}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Criteria Button */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
<Button onClick={addCriteria} variant="outline" className="w-full bg-transparent">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
新增評分項目
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Button onClick={saveCriteria} className="flex-1">
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
儲存評分標準
|
||||
</Button>
|
||||
<Button onClick={resetToDefault} variant="outline">
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
重置為預設
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Preview */}
|
||||
<Card className="mt-8">
|
||||
<CardHeader>
|
||||
<CardTitle>預覽</CardTitle>
|
||||
<CardDescription>以下是您設定的評分標準預覽</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{criteria.map((item, index) => (
|
||||
<div key={item.id} className="flex items-center justify-between p-3 bg-muted rounded-lg">
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{item.name || `評分項目 ${index + 1}`}</div>
|
||||
{item.description && <div className="text-sm text-muted-foreground mt-1">{item.description}</div>}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-medium">權重: {item.weight}%</div>
|
||||
<div className="text-sm text-muted-foreground">滿分: {item.maxScore}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
126
app/globals.css
Normal file
126
app/globals.css
Normal file
@@ -0,0 +1,126 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
/* 更新為 AI 評審系統的專業色彩配置 */
|
||||
--background: oklch(1 0 0); /* #ffffff */
|
||||
--foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--card: oklch(0.98 0 0); /* #f9fafb */
|
||||
--card-foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--popover: oklch(1 0 0); /* #ffffff */
|
||||
--popover-foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--primary: oklch(0.55 0.15 200); /* #0891b2 cyan-600 */
|
||||
--primary-foreground: oklch(1 0 0); /* #ffffff */
|
||||
--secondary: oklch(0.6 0.2 260); /* #6366f1 blue */
|
||||
--secondary-foreground: oklch(1 0 0); /* #ffffff */
|
||||
--muted: oklch(0.98 0 0); /* #f9fafb */
|
||||
--muted-foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--accent: oklch(0.6 0.2 260); /* #6366f1 blue */
|
||||
--accent-foreground: oklch(1 0 0); /* #ffffff */
|
||||
--destructive: oklch(0.577 0.245 27.325); /* #dc2626 */
|
||||
--destructive-foreground: oklch(1 0 0); /* #ffffff */
|
||||
--border: oklch(0.9 0 0); /* #e5e7eb */
|
||||
--input: oklch(1 0 0); /* #ffffff */
|
||||
--ring: oklch(0.55 0.15 200 / 0.5); /* rgba(8, 145, 178, 0.5) */
|
||||
--chart-1: oklch(0.6 0.2 260); /* #6366f1 */
|
||||
--chart-2: oklch(0.577 0.245 27.325); /* #dc2626 */
|
||||
--chart-3: oklch(0.55 0.15 200); /* #0891b2 */
|
||||
--chart-4: oklch(0.7 0.2 80); /* #f59e0b */
|
||||
--chart-5: oklch(0.35 0 0); /* #4b5563 */
|
||||
--radius: 0.5rem;
|
||||
--sidebar: oklch(0.98 0 0); /* #f9fafb */
|
||||
--sidebar-foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--sidebar-primary: oklch(1 0 0); /* #ffffff */
|
||||
--sidebar-primary-foreground: oklch(0.35 0 0); /* #4b5563 */
|
||||
--sidebar-accent: oklch(0.6 0.2 260); /* #6366f1 */
|
||||
--sidebar-accent-foreground: oklch(1 0 0); /* #ffffff */
|
||||
--sidebar-border: oklch(0.9 0 0); /* #e5e7eb */
|
||||
--sidebar-ring: oklch(0.55 0.15 200 / 0.5); /* rgba(8, 145, 178, 0.5) */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.55 0.15 200);
|
||||
--primary-foreground: oklch(1 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.439 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
3
app/history/loading.tsx
Normal file
3
app/history/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
260
app/history/page.tsx
Normal file
260
app/history/page.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Sidebar } from "@/components/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { FileText, Calendar, Search, Eye, Download, Trash2 } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
// 模擬歷史記錄數據
|
||||
const mockHistory = [
|
||||
{
|
||||
id: "1",
|
||||
title: "產品介紹簡報",
|
||||
type: "PPT",
|
||||
score: 82,
|
||||
grade: "B+",
|
||||
date: "2024-01-15",
|
||||
status: "completed",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "市場分析報告",
|
||||
type: "Website",
|
||||
score: 76,
|
||||
grade: "B",
|
||||
date: "2024-01-12",
|
||||
status: "completed",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "產品演示影片",
|
||||
type: "Video",
|
||||
score: 88,
|
||||
grade: "A-",
|
||||
date: "2024-01-10",
|
||||
status: "completed",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "技術架構說明",
|
||||
type: "PPT",
|
||||
score: 0,
|
||||
grade: "-",
|
||||
date: "2024-01-08",
|
||||
status: "processing",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "用戶體驗設計",
|
||||
type: "Website",
|
||||
score: 91,
|
||||
grade: "A",
|
||||
date: "2024-01-05",
|
||||
status: "completed",
|
||||
},
|
||||
]
|
||||
|
||||
export default function HistoryPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [filterType, setFilterType] = useState("all")
|
||||
const [filterStatus, setFilterStatus] = useState("all")
|
||||
|
||||
const filteredHistory = mockHistory.filter((item) => {
|
||||
const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesType = filterType === "all" || item.type === filterType
|
||||
const matchesStatus = filterStatus === "all" || item.status === filterStatus
|
||||
return matchesSearch && matchesType && matchesStatus
|
||||
})
|
||||
|
||||
const getGradeColor = (grade: string) => {
|
||||
if (grade.startsWith("A")) return "bg-green-100 text-green-800"
|
||||
if (grade.startsWith("B")) return "bg-blue-100 text-blue-800"
|
||||
if (grade.startsWith("C")) return "bg-yellow-100 text-yellow-800"
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "PPT":
|
||||
return <FileText className="h-4 w-4" />
|
||||
case "Video":
|
||||
return <FileText className="h-4 w-4" />
|
||||
case "Website":
|
||||
return <FileText className="h-4 w-4" />
|
||||
default:
|
||||
return <FileText className="h-4 w-4" />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Sidebar />
|
||||
|
||||
<main className="md:ml-64 p-6">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8 pt-8 md:pt-0">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]">歷史記錄</h1>
|
||||
<p className="text-muted-foreground">查看您的所有評審記錄和結果</p>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜尋專案標題..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Select value={filterType} onValueChange={setFilterType}>
|
||||
<SelectTrigger className="w-full sm:w-40">
|
||||
<SelectValue placeholder="類型篩選" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有類型</SelectItem>
|
||||
<SelectItem value="PPT">PPT</SelectItem>
|
||||
<SelectItem value="Video">影片</SelectItem>
|
||||
<SelectItem value="Website">網站</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={filterStatus} onValueChange={setFilterStatus}>
|
||||
<SelectTrigger className="w-full sm:w-40">
|
||||
<SelectValue placeholder="狀態篩選" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有狀態</SelectItem>
|
||||
<SelectItem value="completed">已完成</SelectItem>
|
||||
<SelectItem value="processing">處理中</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Statistics */}
|
||||
<div className="grid md:grid-cols-4 gap-4 mb-6">
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<div className="text-2xl font-bold text-primary mb-1">{mockHistory.length}</div>
|
||||
<div className="text-sm text-muted-foreground">總評審數</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">
|
||||
{mockHistory.filter((item) => item.status === "completed").length}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">已完成</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<div className="text-2xl font-bold text-blue-600 mb-1">
|
||||
{Math.round(
|
||||
mockHistory.filter((item) => item.score > 0).reduce((sum, item) => sum + item.score, 0) /
|
||||
mockHistory.filter((item) => item.score > 0).length,
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">平均分數</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<div className="text-2xl font-bold text-yellow-600 mb-1">
|
||||
{mockHistory.filter((item) => item.status === "processing").length}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">處理中</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* History List */}
|
||||
<div className="space-y-4">
|
||||
{filteredHistory.map((item) => (
|
||||
<Card key={item.id} className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center">
|
||||
{getTypeIcon(item.type)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium truncate">{item.title}</h3>
|
||||
<div className="flex items-center gap-4 mt-1">
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{item.date}
|
||||
</div>
|
||||
<Badge variant="outline">{item.type}</Badge>
|
||||
<Badge variant={item.status === "completed" ? "default" : "secondary"}>
|
||||
{item.status === "completed" ? "已完成" : "處理中"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{item.status === "completed" && (
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-primary">{item.score}</div>
|
||||
<Badge className={getGradeColor(item.grade)}>{item.grade}</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
{item.status === "completed" ? (
|
||||
<>
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={`/results?id=${item.id}`}>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button variant="outline" size="sm" disabled>
|
||||
處理中...
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" className="text-destructive hover:text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredHistory.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center py-12">
|
||||
<FileText className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="font-medium mb-2">沒有找到相關記錄</h3>
|
||||
<p className="text-muted-foreground mb-4">嘗試調整搜尋條件或篩選器</p>
|
||||
<Button asChild>
|
||||
<Link href="/upload">開始新的評審</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
33
app/layout.tsx
Normal file
33
app/layout.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import { Playfair_Display } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import ClientLayout from "./client-layout"
|
||||
|
||||
const playfairDisplay = Playfair_Display({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-playfair",
|
||||
weight: ["400", "700"],
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "AI 評審系統",
|
||||
description: "智能評審平台 - 上傳 PPT、影片和網站連結,獲得專業評分結果",
|
||||
generator: "v0.app",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="zh-TW">
|
||||
<body className={`font-sans ${GeistSans.variable} ${GeistMono.variable} ${playfairDisplay.variable}`}>
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
150
app/page.tsx
Normal file
150
app/page.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Sidebar } from "@/components/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Upload, Settings, BarChart3, FileText, ArrowRight } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Sidebar />
|
||||
|
||||
<main className="md:ml-64 p-6">
|
||||
{/* Hero Section */}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-12 pt-8 md:pt-0">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-4 font-[var(--font-playfair)]">
|
||||
AI 智能評審系統
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
|
||||
上傳您的 PPT、影片或網站連結,設定評分標準,獲得專業的 AI 評分結果
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button asChild size="lg" className="text-lg px-8">
|
||||
<Link href="/upload">
|
||||
開始評審 <ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="text-lg px-8 bg-transparent">
|
||||
<Link href="/criteria">設定標準</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
<Card className="text-center hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Upload className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">多格式上傳</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>支援 PPT、影片、網站連結等多種格式的內容上傳</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="text-center hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="w-12 h-12 bg-secondary/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Settings className="h-6 w-6 text-secondary" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">彈性評分標準</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>自定義評分項目和權重,建立符合需求的評審標準</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="text-center hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="w-12 h-12 bg-accent/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<BarChart3 className="h-6 w-6 text-accent" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">智能評分</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>AI 分析內容並根據您的標準提供詳細的評分結果</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="text-center hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="w-12 h-12 bg-chart-4/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<FileText className="h-6 w-6 text-chart-4" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">詳細報告</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>獲得完整的評分報告和改進建議,追蹤歷史記錄</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* How it works */}
|
||||
<Card className="mb-12">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-2xl font-[var(--font-playfair)]">使用流程</CardTitle>
|
||||
<CardDescription className="text-lg">簡單四步驟,完成專業評審</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid md:grid-cols-4 gap-8">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center mx-auto mb-4 text-primary-foreground font-bold text-xl">
|
||||
1
|
||||
</div>
|
||||
<h3 className="font-semibold mb-2">設定評分標準</h3>
|
||||
<p className="text-sm text-muted-foreground">定義評分項目和權重比例</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-secondary rounded-full flex items-center justify-center mx-auto mb-4 text-secondary-foreground font-bold text-xl">
|
||||
2
|
||||
</div>
|
||||
<h3 className="font-semibold mb-2">上傳內容</h3>
|
||||
<p className="text-sm text-muted-foreground">上傳 PPT、影片或提供網站連結</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-accent rounded-full flex items-center justify-center mx-auto mb-4 text-accent-foreground font-bold text-xl">
|
||||
3
|
||||
</div>
|
||||
<h3 className="font-semibold mb-2">AI 分析</h3>
|
||||
<p className="text-sm text-muted-foreground">系統自動分析內容並評分</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-chart-4 rounded-full flex items-center justify-center mx-auto mb-4 text-white font-bold text-xl">
|
||||
4
|
||||
</div>
|
||||
<h3 className="font-semibold mb-2">查看結果</h3>
|
||||
<p className="text-sm text-muted-foreground">獲得詳細評分報告和建議</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-3xl font-bold text-primary mb-2">10+</div>
|
||||
<p className="text-muted-foreground">評分維度</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-3xl font-bold text-secondary mb-2">100%</div>
|
||||
<p className="text-muted-foreground">客製化標準</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-3xl font-bold text-accent mb-2">24/7</div>
|
||||
<p className="text-muted-foreground">即時評審</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
478
app/results/page.tsx
Normal file
478
app/results/page.tsx
Normal file
@@ -0,0 +1,478 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Sidebar } from "@/components/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
RadarChart,
|
||||
PolarGrid,
|
||||
PolarAngleAxis,
|
||||
PolarRadiusAxis,
|
||||
Radar,
|
||||
} from "recharts"
|
||||
import { Download, Share2, TrendingUp, AlertCircle, CheckCircle, Star } from "lucide-react"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
|
||||
// 模擬評分結果數據
|
||||
const mockResults = {
|
||||
projectTitle: "產品介紹簡報",
|
||||
overallScore: 82,
|
||||
totalPossible: 100,
|
||||
grade: "B+",
|
||||
analysisDate: "2024-01-15",
|
||||
criteria: [
|
||||
{
|
||||
name: "內容品質",
|
||||
score: 8.5,
|
||||
maxScore: 10,
|
||||
weight: 25,
|
||||
weightedScore: 21.25,
|
||||
feedback: "內容結構清晰,資訊豐富且準確。建議增加更多實際案例來支撐論點。",
|
||||
strengths: ["邏輯清晰", "資料準確", "結構完整"],
|
||||
improvements: ["增加案例", "深化分析"],
|
||||
},
|
||||
{
|
||||
name: "視覺設計",
|
||||
score: 7.8,
|
||||
maxScore: 10,
|
||||
weight: 20,
|
||||
weightedScore: 15.6,
|
||||
feedback: "整體設計風格統一,色彩搭配合理。部分頁面文字密度過高,影響閱讀體驗。",
|
||||
strengths: ["風格統一", "色彩協調", "版面整潔"],
|
||||
improvements: ["減少文字密度", "增加視覺元素"],
|
||||
},
|
||||
{
|
||||
name: "邏輯結構",
|
||||
score: 8.8,
|
||||
maxScore: 10,
|
||||
weight: 20,
|
||||
weightedScore: 17.6,
|
||||
feedback: "邏輯架構非常清晰,各章節銜接自然,論述層次分明。",
|
||||
strengths: ["邏輯清晰", "結構完整", "銜接自然"],
|
||||
improvements: ["可增加總結回顧"],
|
||||
},
|
||||
{
|
||||
name: "創新性",
|
||||
score: 7.2,
|
||||
maxScore: 10,
|
||||
weight: 15,
|
||||
weightedScore: 10.8,
|
||||
feedback: "內容具有一定創新性,但可以更大膽地提出獨特觀點和解決方案。",
|
||||
strengths: ["思路新穎", "角度獨特"],
|
||||
improvements: ["增加創新元素", "提出獨特見解"],
|
||||
},
|
||||
{
|
||||
name: "實用性",
|
||||
score: 8.3,
|
||||
maxScore: 10,
|
||||
weight: 20,
|
||||
weightedScore: 16.6,
|
||||
feedback: "內容實用性強,提供的解決方案具有可操作性,對目標受眾有實際價值。",
|
||||
strengths: ["實用性強", "可操作性好", "價值明確"],
|
||||
improvements: ["增加實施步驟"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const chartData = mockResults.criteria.map((item) => ({
|
||||
name: item.name,
|
||||
score: item.score,
|
||||
maxScore: item.maxScore,
|
||||
percentage: (item.score / item.maxScore) * 100,
|
||||
}))
|
||||
|
||||
const pieData = mockResults.criteria.map((item) => ({
|
||||
name: item.name,
|
||||
value: item.weightedScore,
|
||||
weight: item.weight,
|
||||
}))
|
||||
|
||||
const radarData = mockResults.criteria.map((item) => ({
|
||||
subject: item.name,
|
||||
score: item.score,
|
||||
fullMark: item.maxScore,
|
||||
}))
|
||||
|
||||
const COLORS = ["#0891b2", "#6366f1", "#f59e0b", "#dc2626", "#10b981"]
|
||||
|
||||
export default function ResultsPage() {
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
const { toast } = useToast()
|
||||
|
||||
const downloadReport = () => {
|
||||
toast({
|
||||
title: "報告下載中",
|
||||
description: "評審報告 PDF 正在生成,請稍候...",
|
||||
})
|
||||
}
|
||||
|
||||
const shareResults = () => {
|
||||
toast({
|
||||
title: "分享連結已複製",
|
||||
description: "評審結果分享連結已複製到剪貼板",
|
||||
})
|
||||
}
|
||||
|
||||
const getScoreColor = (score: number, maxScore: number) => {
|
||||
const percentage = (score / maxScore) * 100
|
||||
if (percentage >= 90) return "text-green-600"
|
||||
if (percentage >= 80) return "text-blue-600"
|
||||
if (percentage >= 70) return "text-yellow-600"
|
||||
if (percentage >= 60) return "text-orange-600"
|
||||
return "text-red-600"
|
||||
}
|
||||
|
||||
const getGradeColor = (grade: string) => {
|
||||
if (grade.startsWith("A")) return "bg-green-100 text-green-800"
|
||||
if (grade.startsWith("B")) return "bg-blue-100 text-blue-800"
|
||||
if (grade.startsWith("C")) return "bg-yellow-100 text-yellow-800"
|
||||
return "bg-red-100 text-red-800"
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Sidebar />
|
||||
|
||||
<main className="md:ml-64 p-6">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8 pt-8 md:pt-0">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]">評審結果</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{mockResults.projectTitle} - 分析完成於 {mockResults.analysisDate}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={shareResults} variant="outline">
|
||||
<Share2 className="h-4 w-4 mr-2" />
|
||||
分享
|
||||
</Button>
|
||||
<Button onClick={downloadReport}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
下載報告
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overall Score */}
|
||||
<Card className="mb-8">
|
||||
<CardContent className="pt-6">
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="text-4xl font-bold text-primary mb-2">{mockResults.overallScore}</div>
|
||||
<div className="text-sm text-muted-foreground">總分 / {mockResults.totalPossible}</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Badge className={`text-lg px-4 py-2 ${getGradeColor(mockResults.grade)}`}>{mockResults.grade}</Badge>
|
||||
<div className="text-sm text-muted-foreground mt-2">等級評定</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center mb-2">
|
||||
<TrendingUp className="h-6 w-6 text-green-600" />
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">表現良好</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center mb-2">
|
||||
<Star className="h-6 w-6 text-yellow-500" />
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">推薦等級</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Detailed Results */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="overview">總覽</TabsTrigger>
|
||||
<TabsTrigger value="detailed">詳細分析</TabsTrigger>
|
||||
<TabsTrigger value="charts">圖表分析</TabsTrigger>
|
||||
<TabsTrigger value="suggestions">改進建議</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-6">
|
||||
{/* Score Breakdown */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>評分明細</CardTitle>
|
||||
<CardDescription>各項評分標準的得分情況</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{mockResults.criteria.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h4 className="font-medium">{item.name}</h4>
|
||||
<Badge variant="outline">{item.weight}% 權重</Badge>
|
||||
</div>
|
||||
<Progress value={(item.score / item.maxScore) * 100} className="h-2" />
|
||||
</div>
|
||||
<div className="text-right ml-4">
|
||||
<div className={`text-2xl font-bold ${getScoreColor(item.score, item.maxScore)}`}>
|
||||
{item.score}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">/ {item.maxScore}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<CheckCircle className="h-8 w-8 text-green-600 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">3</div>
|
||||
<div className="text-sm text-muted-foreground">優秀項目</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<AlertCircle className="h-8 w-8 text-yellow-600 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-yellow-600 mb-1">2</div>
|
||||
<div className="text-sm text-muted-foreground">待改進項目</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<TrendingUp className="h-8 w-8 text-blue-600 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-blue-600 mb-1">82%</div>
|
||||
<div className="text-sm text-muted-foreground">整體表現</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="detailed" className="space-y-6">
|
||||
{mockResults.criteria.map((item, index) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{item.name}
|
||||
<Badge variant="outline">{item.weight}% 權重</Badge>
|
||||
</CardTitle>
|
||||
<div className={`text-2xl font-bold ${getScoreColor(item.score, item.maxScore)}`}>
|
||||
{item.score}/{item.maxScore}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">AI 評語</h4>
|
||||
<p className="text-muted-foreground leading-relaxed">{item.feedback}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2 text-green-700">優點</h4>
|
||||
<ul className="space-y-1">
|
||||
{item.strengths.map((strength, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
{strength}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2 text-orange-700">改進建議</h4>
|
||||
<ul className="space-y-1">
|
||||
{item.improvements.map((improvement, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm">
|
||||
<AlertCircle className="h-4 w-4 text-orange-600" />
|
||||
{improvement}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="charts" className="space-y-6">
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
{/* Bar Chart */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>各項目得分</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
|
||||
<YAxis domain={[0, 10]} />
|
||||
<Tooltip />
|
||||
<Bar dataKey="score" fill="#0891b2" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Pie Chart */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>權重分布</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={pieData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={({ name, weight }) => `${name} (${weight}%)`}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
{pieData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Radar Chart */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>能力雷達圖</CardTitle>
|
||||
<CardDescription>各項能力的綜合表現分析</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<RadarChart data={radarData}>
|
||||
<PolarGrid />
|
||||
<PolarAngleAxis dataKey="subject" />
|
||||
<PolarRadiusAxis domain={[0, 10]} />
|
||||
<Radar name="得分" dataKey="score" stroke="#0891b2" fill="#0891b2" fillOpacity={0.3} />
|
||||
</RadarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="suggestions" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>整體改進建議</CardTitle>
|
||||
<CardDescription>基於 AI 分析結果的具體改進方向</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h4 className="font-medium mb-3 text-green-700">繼續保持的優勢</h4>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<h5 className="font-medium mb-2">邏輯結構清晰</h5>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
內容組織有序,各部分銜接自然,建議在未來的作品中繼續保持這種清晰的邏輯架構。
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<h5 className="font-medium mb-2">內容品質優秀</h5>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
資訊準確且豐富,專業度高,這是您的核心優勢,請繼續發揮。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-3 text-orange-700">重點改進方向</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-orange-50 rounded-lg">
|
||||
<h5 className="font-medium mb-2">提升創新性</h5>
|
||||
<p className="text-sm text-muted-foreground mb-3">當前創新性得分較低,建議:</p>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>• 嘗試從不同角度分析問題</li>
|
||||
<li>• 引入新的理論或方法</li>
|
||||
<li>• 提出獨特的解決方案</li>
|
||||
<li>• 結合最新的行業趨勢</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-orange-50 rounded-lg">
|
||||
<h5 className="font-medium mb-2">優化視覺設計</h5>
|
||||
<p className="text-sm text-muted-foreground mb-3">視覺呈現有改進空間,建議:</p>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>• 減少單頁文字密度</li>
|
||||
<li>• 增加圖表和視覺元素</li>
|
||||
<li>• 使用更多空白空間</li>
|
||||
<li>• 統一字體和色彩風格</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-3 text-blue-700">下一步行動計劃</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-medium">短期目標(1-2週)</h5>
|
||||
<p className="text-sm text-muted-foreground">重新設計視覺版面,減少文字密度,增加圖表元素</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-medium">中期目標(1個月)</h5>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
研究行業最新趨勢,為內容增加創新元素和獨特觀點
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-medium">長期目標(3個月)</h5>
|
||||
<p className="text-sm text-muted-foreground">建立個人風格的簡報模板,形成獨特的表達方式</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
373
app/upload/page.tsx
Normal file
373
app/upload/page.tsx
Normal file
@@ -0,0 +1,373 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useCallback } from "react"
|
||||
import { Sidebar } from "@/components/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Upload, FileText, Video, LinkIcon, X, CheckCircle, AlertCircle, Play, ExternalLink } from "lucide-react"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { useDropzone } from "react-dropzone"
|
||||
|
||||
interface UploadedFile {
|
||||
id: string
|
||||
name: string
|
||||
size: number
|
||||
type: string
|
||||
status: "uploading" | "completed" | "error"
|
||||
progress: number
|
||||
url?: string
|
||||
}
|
||||
|
||||
export default function UploadPage() {
|
||||
const [files, setFiles] = useState<UploadedFile[]>([])
|
||||
const [websiteUrl, setWebsiteUrl] = useState("")
|
||||
const [projectTitle, setProjectTitle] = useState("")
|
||||
const [projectDescription, setProjectDescription] = useState("")
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false)
|
||||
const { toast } = useToast()
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
const newFiles: UploadedFile[] = acceptedFiles.map((file) => ({
|
||||
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
status: "uploading",
|
||||
progress: 0,
|
||||
}))
|
||||
|
||||
setFiles((prev) => [...prev, ...newFiles])
|
||||
|
||||
// 模擬上傳進度
|
||||
newFiles.forEach((file) => {
|
||||
simulateUpload(file.id)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
"application/vnd.ms-powerpoint": [".ppt"],
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"],
|
||||
"video/*": [".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm"],
|
||||
"application/pdf": [".pdf"],
|
||||
},
|
||||
maxSize: 100 * 1024 * 1024, // 100MB
|
||||
})
|
||||
|
||||
const simulateUpload = (fileId: string) => {
|
||||
const interval = setInterval(() => {
|
||||
setFiles((prev) =>
|
||||
prev.map((file) => {
|
||||
if (file.id === fileId) {
|
||||
const newProgress = Math.min(file.progress + Math.random() * 30, 100)
|
||||
const status = newProgress === 100 ? "completed" : "uploading"
|
||||
return { ...file, progress: newProgress, status }
|
||||
}
|
||||
return file
|
||||
}),
|
||||
)
|
||||
}, 500)
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval)
|
||||
setFiles((prev) =>
|
||||
prev.map((file) => (file.id === fileId ? { ...file, progress: 100, status: "completed" } : file)),
|
||||
)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const removeFile = (fileId: string) => {
|
||||
setFiles((prev) => prev.filter((file) => file.id !== fileId))
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return "0 Bytes"
|
||||
const k = 1024
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"]
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
||||
}
|
||||
|
||||
const getFileIcon = (type: string) => {
|
||||
if (type.includes("presentation") || type.includes("powerpoint")) {
|
||||
return <FileText className="h-8 w-8 text-orange-500" />
|
||||
}
|
||||
if (type.includes("video")) {
|
||||
return <Video className="h-8 w-8 text-blue-500" />
|
||||
}
|
||||
return <FileText className="h-8 w-8 text-gray-500" />
|
||||
}
|
||||
|
||||
const startAnalysis = () => {
|
||||
if (files.length === 0 && !websiteUrl.trim()) {
|
||||
toast({
|
||||
title: "請上傳文件或提供網站連結",
|
||||
description: "至少需要提供一種評審內容",
|
||||
variant: "destructive",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!projectTitle.trim()) {
|
||||
toast({
|
||||
title: "請填寫專案標題",
|
||||
description: "專案標題是必填欄位",
|
||||
variant: "destructive",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setIsAnalyzing(true)
|
||||
|
||||
// 模擬分析過程
|
||||
setTimeout(() => {
|
||||
setIsAnalyzing(false)
|
||||
toast({
|
||||
title: "分析完成",
|
||||
description: "評審結果已生成,請查看結果頁面",
|
||||
})
|
||||
// 這裡會導向到結果頁面
|
||||
window.location.href = "/results"
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Sidebar />
|
||||
|
||||
<main className="md:ml-64 p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8 pt-8 md:pt-0">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]">上傳評審內容</h1>
|
||||
<p className="text-muted-foreground">上傳您的 PPT、影片或提供網站連結,開始 AI 智能評審</p>
|
||||
</div>
|
||||
|
||||
{/* Project Info */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>專案資訊</CardTitle>
|
||||
<CardDescription>請填寫專案的基本資訊,這將幫助 AI 更好地理解評審內容</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="project-title">專案標題 *</Label>
|
||||
<Input
|
||||
id="project-title"
|
||||
value={projectTitle}
|
||||
onChange={(e) => setProjectTitle(e.target.value)}
|
||||
placeholder="輸入專案標題"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="project-description">專案描述</Label>
|
||||
<Textarea
|
||||
id="project-description"
|
||||
value={projectDescription}
|
||||
onChange={(e) => setProjectDescription(e.target.value)}
|
||||
placeholder="簡要描述專案內容、目標或背景(選填)"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Upload Tabs */}
|
||||
<Tabs defaultValue="files" className="mb-6">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="files">文件上傳</TabsTrigger>
|
||||
<TabsTrigger value="website">網站連結</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="files">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Upload className="h-5 w-5" />
|
||||
上傳文件
|
||||
</CardTitle>
|
||||
<CardDescription>支援 PPT、PPTX、MP4、AVI、MOV 等格式,單檔最大 100MB</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Drop Zone */}
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${
|
||||
isDragActive
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-muted-foreground/25 hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Upload className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
{isDragActive ? (
|
||||
<p className="text-primary font-medium">放開以上傳文件</p>
|
||||
) : (
|
||||
<div>
|
||||
<p className="font-medium mb-2">拖拽文件到此處或點擊選擇</p>
|
||||
<p className="text-sm text-muted-foreground">支援 PPT、影片等多種格式</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File List */}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-6 space-y-3">
|
||||
<h4 className="font-medium">已上傳文件</h4>
|
||||
{files.map((file) => (
|
||||
<div key={file.id} className="flex items-center gap-4 p-4 bg-muted rounded-lg">
|
||||
{getFileIcon(file.type)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="font-medium truncate">{file.name}</p>
|
||||
<Badge
|
||||
variant={
|
||||
file.status === "completed"
|
||||
? "default"
|
||||
: file.status === "error"
|
||||
? "destructive"
|
||||
: "secondary"
|
||||
}
|
||||
>
|
||||
{file.status === "completed" && <CheckCircle className="h-3 w-3 mr-1" />}
|
||||
{file.status === "error" && <AlertCircle className="h-3 w-3 mr-1" />}
|
||||
{file.status === "uploading" ? "上傳中" : file.status === "completed" ? "完成" : "錯誤"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-muted-foreground">{formatFileSize(file.size)}</span>
|
||||
{file.status === "uploading" && (
|
||||
<div className="flex-1 max-w-xs">
|
||||
<Progress value={file.progress} className="h-2" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeFile(file.id)}
|
||||
className="text-muted-foreground hover:text-destructive"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="website">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<LinkIcon className="h-5 w-5" />
|
||||
網站連結
|
||||
</CardTitle>
|
||||
<CardDescription>提供網站 URL,AI 將分析網站內容進行評審</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="website-url">網站 URL</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="website-url"
|
||||
value={websiteUrl}
|
||||
onChange={(e) => setWebsiteUrl(e.target.value)}
|
||||
placeholder="https://example.com"
|
||||
type="url"
|
||||
/>
|
||||
<Button variant="outline" size="icon">
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{websiteUrl && (
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<LinkIcon className="h-4 w-4 text-primary" />
|
||||
<span className="font-medium">預覽網站</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground truncate">{websiteUrl}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Analysis Button */}
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<Button onClick={startAnalysis} disabled={isAnalyzing} size="lg" className="w-full sm:w-auto px-8">
|
||||
{isAnalyzing ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
|
||||
分析中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
開始 AI 評審
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{isAnalyzing && (
|
||||
<div className="mt-4 max-w-md mx-auto">
|
||||
<div className="flex items-center justify-between text-sm text-muted-foreground mb-2">
|
||||
<span>正在分析內容...</span>
|
||||
<span>預計 3-5 分鐘</span>
|
||||
</div>
|
||||
<Progress value={33} className="h-2" />
|
||||
<p className="text-xs text-muted-foreground mt-2">AI 正在深度分析您的內容,請稍候</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Tips */}
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">上傳小貼士</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">文件格式建議</h4>
|
||||
<ul className="space-y-1 text-muted-foreground">
|
||||
<li>• PPT/PPTX:確保內容清晰完整</li>
|
||||
<li>• 影片:建議 1080p 以上畫質</li>
|
||||
<li>• 檔案大小:建議不超過 50MB</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">網站連結注意事項</h4>
|
||||
<ul className="space-y-1 text-muted-foreground">
|
||||
<li>• 確保網站可正常訪問</li>
|
||||
<li>• 避免需要登入的頁面</li>
|
||||
<li>• 建議提供完整的 URL</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user