"use client"; import { useState, useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Sparkles, Calendar, Type, Loader2, Check, Circle, Rocket, PenTool, Wand2, Eye, AlertCircle, RefreshCw, } from "lucide-react"; import { contentsApi, contentGenerationApi, knowledgeApi, type Content, type KnowledgeBase, } from "@/lib/api"; // ─── Types ─────────────────────────────────────────────────────────────────── type PipelineStepStatus = "completed" | "active" | "pending"; interface PipelineStep { id: string; title: string; status: PipelineStepStatus; } // ─── Static Options ─────────────────────────────────────────────────────────── const platformOptions = [ { value: "wechat", label: "微信公众号" }, { value: "zhihu", label: "知乎" }, { value: "xiaohongshu", label: "小红书" }, { value: "baijiahao", label: "百家号" }, { value: "douyin", label: "抖音" }, { value: "general", label: "通用" }, ]; const styleOptions = [ { value: "professional", label: "专业严谨" }, { value: "casual", label: "轻松活泼" }, { value: "academic", label: "学术深度" }, ]; const wordCountOptions = [ { value: "1000", label: "1000字" }, { value: "2000", label: "2000字" }, { value: "3000", label: "3000字" }, { value: "5000", label: "5000字" }, ]; const pipelineStepsTemplate: PipelineStep[] = [ { id: "topic", title: "选题分析", status: "pending" }, { id: "generate", title: "内容生成", status: "pending" }, { id: "deai", title: "去AI化处理", status: "pending" }, { id: "geo", title: "GEO优化", status: "pending" }, { id: "rule", title: "规则验证", status: "pending" }, { id: "media", title: "多媒体内容插入", status: "pending" }, ]; // ─── Helpers ───────────────────────────────────────────────────────────────── function getStatusBadgeConfig(status: Content["status"]) { switch (status) { case "draft": return { label: "草稿", className: "bg-gray-50 text-gray-600 border-gray-200" }; case "review": return { label: "待审核", className: "bg-purple-50 text-purple-600 border-purple-200" }; case "approved": return { label: "已审核", className: "bg-blue-50 text-blue-600 border-blue-200" }; case "published": return { label: "已发布", className: "bg-primary/10 text-primary border-primary/20" }; case "archived": return { label: "已归档", className: "bg-slate-50 text-slate-600 border-slate-200" }; default: return { label: "未知", className: "bg-muted text-muted-foreground" }; } } function getContentTypeBadge(type: Content["content_type"]) { const map: Record = { article: "bg-blue-50 text-blue-700 border-blue-200", qa: "bg-green-50 text-green-700 border-green-200", knowledge_base: "bg-amber-50 text-amber-700 border-amber-200", social_post: "bg-red-50 text-red-700 border-red-200", other: "bg-gray-50 text-gray-600 border-gray-200", }; const labelMap: Record = { article: "文章", qa: "问答", knowledge_base: "知识库", social_post: "社媒", other: "其他", }; return { className: map[type] ?? map.other, label: labelMap[type] ?? type }; } // ─── Sub-components ────────────────────────────────────────────────────────── function ContentCard({ item }: { item: Content }) { const statusConfig = getStatusBadgeConfig(item.status); const typeConfig = getContentTypeBadge(item.content_type); const wordCount = item.body ? item.body.length : 0; const dateStr = new Date(item.created_at).toLocaleDateString("zh-CN"); return (

{item.title}

{typeConfig.label} {statusConfig.label}
{wordCount > 0 ? `${wordCount} 字` : "暂无内容"} {dateStr}
{item.tags && item.tags.length > 0 && ( {item.tags.slice(0, 2).join(", ")} )}
); } function PipelineTimeline({ steps }: { steps: PipelineStep[] }) { return (
{steps.map((step, idx) => { const isLast = idx === steps.length - 1; const isCompleted = step.status === "completed"; const isActive = step.status === "active"; const isPending = step.status === "pending"; return (
{isCompleted && } {isActive && } {isPending && }
{!isLast && (
)}
{step.title} {isCompleted && 完成} {isActive && 进行中} {isPending && 等待中}
); })}
); } function EmptyState({ onGenerate }: { onGenerate: () => void }) { return (

还没有内容

让AI帮你创作第一篇内容,开启智能内容生产之旅

); } // ─── Main Page ─────────────────────────────────────────────────────────────── export default function ContentPage() { const router = useRouter(); // Data states const [contents, setContents] = useState([]); const [knowledgeBases, setKnowledgeBases] = useState([]); const [pageLoading, setPageLoading] = useState(true); const [pageError, setPageError] = useState(null); // Dialog states const [dialogOpen, setDialogOpen] = useState(false); const [dialogMode, setDialogMode] = useState<"form" | "pipeline" | "result">("form"); // Form states const [keyword, setKeyword] = useState(""); const [platform, setPlatform] = useState(""); const [selectedKbs, setSelectedKbs] = useState([]); const [style, setStyle] = useState("professional"); const [wordCount, setWordCount] = useState("2000"); const [isSubmitting, setIsSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); const [generatedContent, setGeneratedContent] = useState(null); // Pipeline states const [pipelineSteps, setPipelineSteps] = useState( pipelineStepsTemplate.map((s) => ({ ...s })) ); // Fetch content list and knowledge bases on mount useEffect(() => { async function fetchPageData() { try { setPageLoading(true); setPageError(null); const [contentList, kbList] = await Promise.all([ contentsApi.list(), knowledgeApi.listBases(undefined, "enterprise"), ]); setContents(contentList ?? []); setKnowledgeBases(kbList ?? []); } catch (err) { console.error("Content page fetch error:", err); setPageError(err instanceof Error ? err.message : "数据加载失败"); } finally { setPageLoading(false); } } fetchPageData(); }, []); const resetForm = useCallback(() => { setKeyword(""); setPlatform(""); setSelectedKbs([]); setStyle("professional"); setWordCount("2000"); setDialogMode("form"); setPipelineSteps(pipelineStepsTemplate.map((s) => ({ ...s }))); setIsSubmitting(false); setSubmitError(null); setGeneratedContent(null); }, []); const handleOpenDialog = useCallback(() => { resetForm(); setDialogOpen(true); }, [resetForm]); const handleCloseDialog = useCallback(() => { setDialogOpen(false); setTimeout(() => resetForm(), 300); }, [resetForm]); const handleSubmit = useCallback(async () => { if (!keyword.trim() || !platform) return; setIsSubmitting(true); setSubmitError(null); setDialogMode("pipeline"); // Start pipeline animation const steps = pipelineStepsTemplate.map((s) => ({ ...s })); steps[0].status = "active"; setPipelineSteps([...steps]); try { // Call real API const result = await contentGenerationApi.generateContent(undefined, { target_keyword: keyword.trim(), target_platform: platform, knowledge_base_ids: selectedKbs.length > 0 ? selectedKbs : undefined, content_style: style, word_count: parseInt(wordCount, 10), run_deai: true, run_geo: true, }); // Animate pipeline steps based on result const stageMap: Record = { topic_analysis: 0, content_generation: 1, deai_processing: 2, geo_optimization: 3, rule_validation: 4, media_insertion: 5, }; if (result.pipeline_stages) { for (let i = 0; i < result.pipeline_stages.length; i++) { const stage = result.pipeline_stages[i]; const stepIdx = stageMap[stage.stage] ?? i; await new Promise((resolve) => { setTimeout(() => { setPipelineSteps((prev) => prev.map((s, idx) => ({ ...s, status: idx < stepIdx ? "completed" : idx === stepIdx ? "active" : "pending", })) ); resolve(); }, 600 * i); }); } } // Mark all completed await new Promise((resolve) => setTimeout(resolve, 800)); setPipelineSteps(pipelineStepsTemplate.map((s) => ({ ...s, status: "completed" }))); setGeneratedContent(result.optimized_content || result.content); // Save content to backend try { const saved = await contentsApi.create(undefined, { title: keyword.trim(), body: result.optimized_content || result.content, content_type: "article", tags: [platform, keyword.trim()], }); setContents((prev) => [saved, ...prev]); } catch { // Ignore save error, content was still generated } setDialogMode("result"); } catch (err) { console.error("Content generation error:", err); setSubmitError(err instanceof Error ? err.message : "内容生成失败,请重试"); setDialogMode("form"); } finally { setIsSubmitting(false); } }, [keyword, platform, selectedKbs, style, wordCount]); const toggleKb = useCallback((kbId: string) => { setSelectedKbs((prev) => prev.includes(kbId) ? prev.filter((id) => id !== kbId) : [...prev, kbId] ); }, []); const isFormValid = keyword.trim() && platform; // Loading skeleton if (pageLoading) { return (
{Array.from({ length: 4 }).map((_, i) => ( ))}
); } return (
{/* ── Top Area ── */}

内容工坊

AI驱动的内容生产流水线

{/* ── Error Banner ── */} {pageError && (
{pageError}
)} {/* ── Content List ── */} {!pageError && contents.length === 0 ? ( ) : (
{contents.map((item) => ( ))}
)} {/* ── AI Generation Dialog ── */} { if (!open) handleCloseDialog(); }}> {dialogMode === "form" && ( <> AI生成新内容 填写以下信息,AI将为您生成符合品牌调性的优化内容
{submitError && (
{submitError}
)} {/* 目标关键词 */}
setKeyword(e.target.value)} className="rounded-xl" />
{/* 目标平台 */}
{/* 选择知识库 */} {knowledgeBases.length > 0 && (
{knowledgeBases.map((kb) => ( ))}
)} {/* 内容风格 */}
{styleOptions.map((s) => ( ))}
{/* 期望字数 */}
)} {dialogMode === "pipeline" && ( <> 内容生成中 AI正在为您生成优化内容,请稍候...
)} {dialogMode === "result" && ( <>
内容生成完成
AI已成功生成并优化您的内容,您可以立即查看或前往分发
{generatedContent && (
{generatedContent.slice(0, 300)}{generatedContent.length > 300 ? "..." : ""}
)}
({ ...s, status: "completed" as PipelineStepStatus }))} />
)}
); }