image-generation-step.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. "use client"
  2. import { useState, useRef } from "react"
  3. import { Button } from "@/components/ui/button"
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
  5. import { Badge } from "@/components/ui/badge"
  6. import { Progress } from "@/components/ui/progress"
  7. import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
  8. import { Textarea } from "@/components/ui/textarea"
  9. import { Slider } from "@/components/ui/slider"
  10. import { Label } from "@/components/ui/label"
  11. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
  12. import {
  13. Sparkles,
  14. Wand2,
  15. Download,
  16. RefreshCw,
  17. Copy,
  18. Heart,
  19. Share,
  20. Info,
  21. Lightbulb,
  22. AlertCircle,
  23. CheckCircle,
  24. } from "lucide-react"
  25. interface GenerationConfig {
  26. prompt: string
  27. style: string
  28. creativity: number
  29. quality: string
  30. aspectRatio: string
  31. steps: number
  32. }
  33. interface GeneratedImage {
  34. id: string
  35. prompt: string
  36. url: string
  37. style: string
  38. timestamp: Date
  39. liked: boolean
  40. }
  41. interface GenerationState {
  42. isGenerating: boolean
  43. progress: number
  44. currentStep: string
  45. phase: "idle" | "processing" | "generating" | "completed"
  46. }
  47. interface ImageGenerationStepProps {
  48. onNext?: () => void
  49. selectedImages: string[]
  50. }
  51. const presetPrompts = [
  52. {
  53. category: "风景画",
  54. prompts: [
  55. "宁静的山湖倒影,晨雾缭绕,水彩画风格",
  56. "夕阳下的麦田,金黄色调,印象派风格",
  57. "樱花飞舞的小径,粉色花瓣,日式绘画风格",
  58. "雪山峰顶,蓝天白云,油画质感",
  59. ],
  60. },
  61. {
  62. category: "人物肖像",
  63. prompts: [
  64. "优雅的女性肖像,柔和光线,古典绘画风格",
  65. "智慧老者的面容,深邃眼神,素描风格",
  66. "天真儿童的笑容,明亮色彩,卡通风格",
  67. "神秘面具人物,戏剧光影,超现实主义",
  68. ],
  69. },
  70. {
  71. category: "抽象艺术",
  72. prompts: [
  73. "流动的色彩漩涡,渐变效果,抽象表现主义",
  74. "几何图形组合,对比色彩,现代艺术风格",
  75. "音乐的视觉化,律动线条,抽象派风格",
  76. "情感的色彩表达,自由笔触,表现主义",
  77. ],
  78. },
  79. {
  80. category: "幻想世界",
  81. prompts: [
  82. "魔法森林中的精灵,发光植物,奇幻风格",
  83. "漂浮的空中城堡,云海环绕,科幻艺术",
  84. "龙与骑士的史诗对决,史诗场景,概念艺术",
  85. "星空下的神秘法师,魔法光效,奇幻插画",
  86. ],
  87. },
  88. ]
  89. const styles = [
  90. { value: "realistic", label: "写实风格", description: "接近真实照片的效果" },
  91. { value: "impressionist", label: "印象派", description: "光影变化,笔触明显" },
  92. { value: "abstract", label: "抽象派", description: "非具象,注重色彩和形式" },
  93. { value: "watercolor", label: "水彩画", description: "透明感,色彩自然融合" },
  94. { value: "oil-painting", label: "油画", description: "厚重质感,色彩饱满" },
  95. { value: "sketch", label: "素描", description: "线条勾勒,黑白灰调" },
  96. { value: "cartoon", label: "卡通风格", description: "简化形象,色彩鲜明" },
  97. { value: "anime", label: "动漫风格", description: "日式动画特色" },
  98. ]
  99. const qualityOptions = [
  100. { value: "draft", label: "草图质量", description: "快速生成,适合预览" },
  101. { value: "standard", label: "标准质量", description: "平衡速度和质量" },
  102. { value: "high", label: "高质量", description: "精细细节,生成较慢" },
  103. { value: "ultra", label: "超高质量", description: "最佳效果,耗时最长" },
  104. ]
  105. const aspectRatios = [
  106. { value: "1:1", label: "正方形 (1:1)" },
  107. { value: "4:3", label: "标准 (4:3)" },
  108. { value: "16:9", label: "宽屏 (16:9)" },
  109. { value: "3:4", label: "竖屏 (3:4)" },
  110. { value: "9:16", label: "手机 (9:16)" },
  111. ]
  112. export default function ImageGenerationStep({ onNext, selectedImages }: ImageGenerationStepProps) {
  113. const [config, setConfig] = useState<GenerationConfig>({
  114. prompt: "",
  115. style: "realistic",
  116. creativity: 70,
  117. quality: "standard",
  118. aspectRatio: "1:1",
  119. steps: 30,
  120. })
  121. const [generationState, setGenerationState] = useState<GenerationState>({
  122. isGenerating: false,
  123. progress: 0,
  124. currentStep: "",
  125. phase: "idle",
  126. })
  127. const [generatedImages, setGeneratedImages] = useState<GeneratedImage[]>([])
  128. const [selectedCategory, setSelectedCategory] = useState(presetPrompts[0].category)
  129. const intervalRef = useRef<NodeJS.Timeout | null>(null)
  130. const updateConfig = (key: keyof GenerationConfig, value: any) => {
  131. if (!generationState.isGenerating) {
  132. setConfig((prev) => ({ ...prev, [key]: value }))
  133. }
  134. }
  135. const selectPresetPrompt = (prompt: string) => {
  136. if (!generationState.isGenerating) {
  137. setConfig((prev) => ({ ...prev, prompt }))
  138. }
  139. }
  140. const generateImage = async () => {
  141. if (!config.prompt.trim()) return
  142. setGenerationState({
  143. isGenerating: true,
  144. progress: 0,
  145. currentStep: "初始化生成器...",
  146. phase: "processing",
  147. })
  148. try {
  149. // 使用 Pollinations AI 免费API生成图像
  150. const steps = [
  151. { step: "解析提示词...", duration: 500 },
  152. { step: "连接AI服务...", duration: 800 },
  153. { step: "生成图像中...", duration: 2000 },
  154. { step: "优化图像质量...", duration: 1000 },
  155. { step: "完成生成...", duration: 300 },
  156. ]
  157. let totalProgress = 0
  158. const progressIncrement = 80 / steps.length // 留20%给实际生成
  159. // 模拟前期处理步骤
  160. for (let i = 0; i < steps.length - 1; i++) {
  161. setGenerationState((prev) => ({
  162. ...prev,
  163. currentStep: steps[i].step,
  164. phase: "processing",
  165. }))
  166. await new Promise((resolve) => setTimeout(resolve, steps[i].duration))
  167. totalProgress += progressIncrement
  168. setGenerationState((prev) => ({ ...prev, progress: totalProgress }))
  169. }
  170. // 开始实际生成
  171. setGenerationState((prev) => ({
  172. ...prev,
  173. currentStep: "生成图像中...",
  174. phase: "generating",
  175. progress: 80,
  176. }))
  177. // 构建API请求
  178. const enhancedPrompt = `${config.prompt}, ${config.style} style, high quality, detailed`
  179. const encodedPrompt = encodeURIComponent(enhancedPrompt)
  180. // 根据宽高比设置尺寸
  181. const dimensions = {
  182. "1:1": { width: 512, height: 512 },
  183. "4:3": { width: 512, height: 384 },
  184. "16:9": { width: 512, height: 288 },
  185. "3:4": { width: 384, height: 512 },
  186. "9:16": { width: 288, height: 512 },
  187. }
  188. const { width, height } = dimensions[config.aspectRatio as keyof typeof dimensions] || { width: 512, height: 512 }
  189. // 使用 Pollinations AI API
  190. const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=${width}&height=${height}&seed=${Date.now()}&enhance=true&nologo=true`
  191. // 预加载图像以确保生成完成
  192. const img = new Image()
  193. img.crossOrigin = "anonymous"
  194. await new Promise((resolve, reject) => {
  195. img.onload = resolve
  196. img.onerror = reject
  197. img.src = imageUrl
  198. })
  199. setGenerationState((prev) => ({ ...prev, progress: 100 }))
  200. const newImage: GeneratedImage = {
  201. id: `img-${Date.now()}`,
  202. prompt: config.prompt,
  203. url: imageUrl,
  204. style: config.style,
  205. timestamp: new Date(),
  206. liked: false,
  207. }
  208. setGeneratedImages((prev) => [newImage, ...prev])
  209. setGenerationState({
  210. isGenerating: false,
  211. progress: 100,
  212. currentStep: "生成完成!",
  213. phase: "completed",
  214. })
  215. // 3秒后重置状态
  216. setTimeout(() => {
  217. setGenerationState((prev) => ({ ...prev, phase: "idle", progress: 0, currentStep: "" }))
  218. }, 3000)
  219. } catch (error) {
  220. console.error("[v0] Image generation failed:", error)
  221. setGenerationState({
  222. isGenerating: false,
  223. progress: 0,
  224. currentStep: "生成失败,请重试",
  225. phase: "idle",
  226. })
  227. }
  228. }
  229. const downloadImage = async (imageUrl: string, prompt: string) => {
  230. try {
  231. const response = await fetch(imageUrl)
  232. const blob = await response.blob()
  233. const url = window.URL.createObjectURL(blob)
  234. const link = document.createElement("a")
  235. link.href = url
  236. link.download = `ai-artwork-${Date.now()}.png`
  237. document.body.appendChild(link)
  238. link.click()
  239. document.body.removeChild(link)
  240. window.URL.revokeObjectURL(url)
  241. } catch (error) {
  242. console.error("[v0] Download failed:", error)
  243. // 如果下载失败,打开新窗口显示图像
  244. window.open(imageUrl, "_blank")
  245. }
  246. }
  247. const toggleLike = (imageId: string) => {
  248. setGeneratedImages((prev) => prev.map((img) => (img.id === imageId ? { ...img, liked: !img.liked } : img)))
  249. }
  250. const copyPrompt = (prompt: string) => {
  251. navigator.clipboard.writeText(prompt)
  252. }
  253. const getQualityTime = (quality: string) => {
  254. const times = { draft: "10秒", standard: "30秒", high: "60秒", ultra: "120秒" }
  255. return times[quality as keyof typeof times] || "30秒"
  256. }
  257. const getLearnedCategories = () => {
  258. const categories = {
  259. landscape: { name: "风景画", learned: false, count: 0 },
  260. portrait: { name: "人物肖像", learned: false, count: 0 },
  261. abstract: { name: "抽象艺术", learned: false, count: 0 },
  262. stilllife: { name: "静物画", learned: false, count: 0 },
  263. }
  264. // 分析选中的图像属于哪些类别
  265. selectedImages.forEach((imageUrl) => {
  266. if (
  267. imageUrl.includes("mountain") ||
  268. imageUrl.includes("landscape") ||
  269. imageUrl.includes("forest") ||
  270. imageUrl.includes("sunset") ||
  271. imageUrl.includes("lake") ||
  272. imageUrl.includes("desert")
  273. ) {
  274. categories.landscape.count++
  275. categories.landscape.learned = true
  276. }
  277. if (
  278. imageUrl.includes("portrait") ||
  279. imageUrl.includes("woman") ||
  280. imageUrl.includes("man") ||
  281. imageUrl.includes("child") ||
  282. imageUrl.includes("artist")
  283. ) {
  284. categories.portrait.count++
  285. categories.portrait.learned = true
  286. }
  287. if (imageUrl.includes("abstract") || imageUrl.includes("geometric") || imageUrl.includes("colorful")) {
  288. categories.abstract.count++
  289. categories.abstract.learned = true
  290. }
  291. if (
  292. imageUrl.includes("still-life") ||
  293. imageUrl.includes("floral") ||
  294. imageUrl.includes("fruit") ||
  295. imageUrl.includes("vase") ||
  296. imageUrl.includes("books")
  297. ) {
  298. categories.stilllife.count++
  299. categories.stilllife.learned = true
  300. }
  301. })
  302. return categories
  303. }
  304. const learnedCategories = getLearnedCategories()
  305. return (
  306. <div className="space-y-6">
  307. {/* Introduction */}
  308. <Card className="bg-chart-4/5 border-chart-4/20">
  309. <CardHeader>
  310. <div className="flex items-center gap-2">
  311. <Info className="w-5 h-5 text-chart-4" />
  312. <CardTitle className="text-lg font-serif">什么是AI图像生成?</CardTitle>
  313. </div>
  314. </CardHeader>
  315. <CardContent>
  316. <p className="text-muted-foreground leading-relaxed">
  317. AI图像生成是人工智能的创造性应用。通过前面学习的数据和训练,AI模型已经掌握了图像的特征和规律。
  318. 现在,你只需要用文字描述想要的画面,AI就能根据学到的知识创作出全新的艺术作品。
  319. 这就像一位艺术家根据你的描述来绘画,但速度更快,创意无限。
  320. </p>
  321. {/* 使用提示 */}
  322. <div className="mt-4 p-3 bg-blue-50 rounded-lg border border-blue-200">
  323. <div className="flex items-start gap-2">
  324. <AlertCircle className="w-4 h-4 text-blue-600 mt-0.5 flex-shrink-0" />
  325. <p className="text-sm text-blue-800">
  326. <strong>使用提示:</strong>
  327. 详细描述你想要的画面,包括主题、风格、色彩等元素,AI会根据你的描述生成独特的艺术作品。生成过程需要几秒钟时间,请耐心等待。
  328. </p>
  329. </div>
  330. </div>
  331. </CardContent>
  332. </Card>
  333. {/* Learning Status Card */}
  334. <Card className="bg-gradient-to-r from-green-50 to-blue-50 border-green-200">
  335. <CardHeader>
  336. <div className="flex items-center gap-2">
  337. <Lightbulb className="w-5 h-5 text-green-600" />
  338. <CardTitle className="text-lg font-serif">AI学习状态</CardTitle>
  339. </div>
  340. </CardHeader>
  341. <CardContent>
  342. <p className="text-muted-foreground mb-4">
  343. 基于你在数据采集阶段选择的 {selectedImages.length} 张图像,AI已经学习了以下艺术类别:
  344. </p>
  345. <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
  346. {Object.entries(learnedCategories).map(([key, category]) => (
  347. <div
  348. key={key}
  349. className={`p-3 rounded-lg border text-center ${
  350. category.learned
  351. ? "bg-green-100 border-green-300 text-green-800"
  352. : "bg-gray-100 border-gray-300 text-gray-600"
  353. }`}
  354. >
  355. <div className="font-semibold text-sm">{category.name}</div>
  356. <div className="text-xs mt-1">
  357. {category.learned ? (
  358. <>
  359. <CheckCircle className="w-3 h-3 inline mr-1" />
  360. 已学习 ({category.count}张)
  361. </>
  362. ) : (
  363. <>
  364. <AlertCircle className="w-3 h-3 inline mr-1" />
  365. 未学习
  366. </>
  367. )}
  368. </div>
  369. </div>
  370. ))}
  371. </div>
  372. {Object.values(learnedCategories).some((cat) => !cat.learned) && (
  373. <div className="mt-4 p-3 bg-yellow-50 rounded-lg border border-yellow-200">
  374. <div className="flex items-start gap-2">
  375. <AlertCircle className="w-4 h-4 text-yellow-600 mt-0.5 flex-shrink-0" />
  376. <p className="text-sm text-yellow-800">
  377. <strong>提示:</strong>
  378. AI在未学习的类别中生成图像效果可能不如意。建议创作已学习类别的内容以获得更好的效果。
  379. </p>
  380. </div>
  381. </div>
  382. )}
  383. </CardContent>
  384. </Card>
  385. <div className="grid lg:grid-cols-3 gap-6">
  386. {/* Generation Controls */}
  387. <Card className="lg:col-span-1">
  388. <CardHeader>
  389. <CardTitle className="font-serif">创作控制台</CardTitle>
  390. <CardDescription>输入提示词,调整参数,开始AI创作</CardDescription>
  391. </CardHeader>
  392. <CardContent className="space-y-6">
  393. {/* Prompt Input */}
  394. <div className="space-y-3">
  395. <Label className="font-medium">创作提示词</Label>
  396. <Textarea
  397. placeholder="描述你想要AI创作的画面,比如:宁静的山湖倒影,晨雾缭绕,水彩画风格"
  398. value={config.prompt}
  399. onChange={(e) => updateConfig("prompt", e.target.value)}
  400. disabled={generationState.isGenerating}
  401. className="min-h-20 resize-none"
  402. />
  403. <p className="text-xs text-muted-foreground">详细的描述能帮助AI更好地理解你的创意</p>
  404. </div>
  405. {/* Style Selection */}
  406. <div className="space-y-2">
  407. <Label className="font-medium">艺术风格</Label>
  408. <Select
  409. value={config.style}
  410. onValueChange={(value) => updateConfig("style", value)}
  411. disabled={generationState.isGenerating}
  412. >
  413. <SelectTrigger>
  414. <SelectValue />
  415. </SelectTrigger>
  416. <SelectContent>
  417. {styles.map((style) => (
  418. <SelectItem key={style.value} value={style.value}>
  419. <div>
  420. <div className="font-medium">{style.label}</div>
  421. <div className="text-xs text-muted-foreground">{style.description}</div>
  422. </div>
  423. </SelectItem>
  424. ))}
  425. </SelectContent>
  426. </Select>
  427. </div>
  428. {/* Quality Selection */}
  429. <div className="space-y-2">
  430. <Label className="font-medium">生成质量</Label>
  431. <Select
  432. value={config.quality}
  433. onValueChange={(value) => updateConfig("quality", value)}
  434. disabled={generationState.isGenerating}
  435. >
  436. <SelectTrigger>
  437. <SelectValue />
  438. </SelectTrigger>
  439. <SelectContent>
  440. {qualityOptions.map((option) => (
  441. <SelectItem key={option.value} value={option.value}>
  442. <div>
  443. <div className="font-medium">{option.label}</div>
  444. <div className="text-xs text-muted-foreground">
  445. {option.description} • 约{getQualityTime(option.value)}
  446. </div>
  447. </div>
  448. </SelectItem>
  449. ))}
  450. </SelectContent>
  451. </Select>
  452. </div>
  453. {/* Aspect Ratio */}
  454. <div className="space-y-2">
  455. <Label className="font-medium">画面比例</Label>
  456. <Select
  457. value={config.aspectRatio}
  458. onValueChange={(value) => updateConfig("aspectRatio", value)}
  459. disabled={generationState.isGenerating}
  460. >
  461. <SelectTrigger>
  462. <SelectValue />
  463. </SelectTrigger>
  464. <SelectContent>
  465. {aspectRatios.map((ratio) => (
  466. <SelectItem key={ratio.value} value={ratio.value}>
  467. {ratio.label}
  468. </SelectItem>
  469. ))}
  470. </SelectContent>
  471. </Select>
  472. </div>
  473. {/* Creativity Slider */}
  474. <div className="space-y-3">
  475. <Label className="font-medium">创意程度: {config.creativity}%</Label>
  476. <Slider
  477. value={[config.creativity]}
  478. onValueChange={([value]) => updateConfig("creativity", value)}
  479. min={0}
  480. max={100}
  481. step={10}
  482. disabled={generationState.isGenerating}
  483. className="w-full"
  484. />
  485. <div className="flex justify-between text-xs text-muted-foreground">
  486. <span>保守</span>
  487. <span>平衡</span>
  488. <span>创新</span>
  489. </div>
  490. </div>
  491. {/* Generate Button */}
  492. <Button
  493. onClick={generateImage}
  494. disabled={generationState.isGenerating || !config.prompt.trim()}
  495. className="w-full font-semibold"
  496. size="lg"
  497. >
  498. {generationState.isGenerating ? (
  499. <>
  500. <RefreshCw className="w-4 h-4 mr-2 animate-spin" />
  501. 生成中...
  502. </>
  503. ) : (
  504. <>
  505. <Wand2 className="w-4 h-4 mr-2" />
  506. 开始创作
  507. </>
  508. )}
  509. </Button>
  510. </CardContent>
  511. </Card>
  512. {/* Generation Preview and Presets */}
  513. <div className="lg:col-span-2 space-y-6">
  514. {/* Generation Status */}
  515. {generationState.phase !== "idle" && (
  516. <Card>
  517. <CardHeader>
  518. <CardTitle className="font-serif">生成进度</CardTitle>
  519. </CardHeader>
  520. <CardContent>
  521. <div className="space-y-4">
  522. <div className="flex items-center gap-3">
  523. <div className="w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center">
  524. {generationState.phase === "completed" ? (
  525. <Sparkles className="w-4 h-4 text-primary" />
  526. ) : (
  527. <RefreshCw className="w-4 h-4 text-primary animate-spin" />
  528. )}
  529. </div>
  530. <div>
  531. <p className="font-medium">{generationState.currentStep}</p>
  532. <p className="text-sm text-muted-foreground">
  533. {generationState.phase === "completed" ? "创作完成!" : "正在处理中..."}
  534. </p>
  535. </div>
  536. </div>
  537. <Progress value={generationState.progress} className="h-2" />
  538. </div>
  539. </CardContent>
  540. </Card>
  541. )}
  542. {/* Preset Prompts */}
  543. <Card>
  544. <CardHeader>
  545. <CardTitle className="font-serif">创意提示词库</CardTitle>
  546. <CardDescription>选择预设提示词快速开始创作</CardDescription>
  547. </CardHeader>
  548. <CardContent>
  549. <Tabs value={selectedCategory} onValueChange={setSelectedCategory}>
  550. <TabsList className="grid w-full grid-cols-4">
  551. {presetPrompts.map((category) => (
  552. <TabsTrigger key={category.category} value={category.category} className="text-xs">
  553. {category.category}
  554. </TabsTrigger>
  555. ))}
  556. </TabsList>
  557. {presetPrompts.map((category) => (
  558. <TabsContent key={category.category} value={category.category} className="space-y-3 mt-4">
  559. <div className="grid gap-2">
  560. {category.prompts.map((prompt, index) => (
  561. <button
  562. key={index}
  563. onClick={() => selectPresetPrompt(prompt)}
  564. disabled={generationState.isGenerating}
  565. className="text-left p-3 rounded-lg border border-border hover:border-primary/50 hover:bg-primary/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
  566. >
  567. <div className="flex items-start justify-between gap-2">
  568. <p className="text-sm leading-relaxed">{prompt}</p>
  569. <Button
  570. size="sm"
  571. variant="ghost"
  572. onClick={(e) => {
  573. e.stopPropagation()
  574. copyPrompt(prompt)
  575. }}
  576. className="flex-shrink-0"
  577. >
  578. <Copy className="w-3 h-3" />
  579. </Button>
  580. </div>
  581. </button>
  582. ))}
  583. </div>
  584. </TabsContent>
  585. ))}
  586. </Tabs>
  587. </CardContent>
  588. </Card>
  589. </div>
  590. </div>
  591. {/* Generated Images Gallery */}
  592. {generatedImages.length > 0 && (
  593. <Card>
  594. <CardHeader>
  595. <CardTitle className="font-serif">创作作品集</CardTitle>
  596. <CardDescription>你的AI艺术创作历史记录</CardDescription>
  597. </CardHeader>
  598. <CardContent>
  599. <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  600. {generatedImages.map((image) => (
  601. <div key={image.id} className="group relative">
  602. <div className="aspect-square bg-muted rounded-lg overflow-hidden">
  603. <img
  604. src={image.url || "/placeholder.svg"}
  605. alt={image.prompt}
  606. className="w-full h-full object-cover"
  607. onError={(e) => {
  608. // 如果图像加载失败,显示占位符
  609. const target = e.target as HTMLImageElement
  610. target.src = `/placeholder.svg?height=512&width=512&query=${encodeURIComponent(image.prompt)}`
  611. }}
  612. />
  613. </div>
  614. {/* Image Overlay */}
  615. <div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center">
  616. <div className="flex gap-2">
  617. <Button size="sm" variant="secondary" onClick={() => toggleLike(image.id)}>
  618. <Heart className={`w-4 h-4 ${image.liked ? "fill-red-500 text-red-500" : ""}`} />
  619. </Button>
  620. <Button size="sm" variant="secondary" onClick={() => downloadImage(image.url, image.prompt)}>
  621. <Download className="w-4 h-4" />
  622. </Button>
  623. <Button size="sm" variant="secondary">
  624. <Share className="w-4 h-4" />
  625. </Button>
  626. </div>
  627. </div>
  628. {/* Image Info */}
  629. <div className="mt-3 space-y-2">
  630. <p className="text-sm font-medium line-clamp-2">{image.prompt}</p>
  631. <div className="flex items-center justify-between text-xs text-muted-foreground">
  632. <Badge variant="outline" className="text-xs">
  633. {styles.find((s) => s.value === image.style)?.label}
  634. </Badge>
  635. <span>{image.timestamp.toLocaleTimeString()}</span>
  636. </div>
  637. </div>
  638. </div>
  639. ))}
  640. </div>
  641. </CardContent>
  642. </Card>
  643. )}
  644. {/* Completion Message */}
  645. <Card className="bg-primary/5 border-primary/20">
  646. <CardContent className="p-6">
  647. <div className="text-center space-y-4">
  648. <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto">
  649. <Sparkles className="w-8 h-8 text-primary" />
  650. </div>
  651. <div>
  652. <h3 className="text-xl font-bold font-serif mb-2">恭喜完成AI绘画学习之旅!</h3>
  653. <p className="text-muted-foreground leading-relaxed max-w-2xl mx-auto">
  654. 你已经完整体验了AI绘画的四个核心步骤:数据采集、数据预处理、模型训练和图像生成。
  655. 现在你了解了AI是如何学习和创作艺术作品的。继续探索,发挥你的创意,让AI成为你的艺术创作伙伴!
  656. </p>
  657. </div>
  658. <div className="flex justify-center gap-4">
  659. <Button variant="outline" onClick={() => window.location.reload()}>
  660. <RefreshCw className="w-4 h-4 mr-2" />
  661. 重新学习
  662. </Button>
  663. <Button onClick={onNext}>
  664. <Lightbulb className="w-4 h-4 mr-2" />
  665. 继续创作
  666. </Button>
  667. </div>
  668. </div>
  669. </CardContent>
  670. </Card>
  671. </div>
  672. )
  673. }