Files
ViGent2/frontend/src/features/home/ui/ScriptLearningModal.tsx
Kevin Wong f879fb0001 更新
2026-03-09 10:18:14 +08:00

243 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { BookOpen, Sparkles } from "lucide-react";
import { AppModal, AppModalHeader } from "@/shared/ui/AppModal";
import { useScriptLearning } from "./script-learning/useScriptLearning";
interface ScriptLearningModalProps {
isOpen: boolean;
onClose: () => void;
onApply?: (text: string) => void;
}
const WORD_COUNT_MIN = 80;
const WORD_COUNT_MAX = 1000;
export default function ScriptLearningModal({ isOpen, onClose, onApply }: ScriptLearningModalProps) {
const {
step,
inputUrl,
setInputUrl,
topics,
selectedTopic,
setSelectedTopic,
wordCount,
setWordCount,
generatedScript,
error,
analysisId,
handleAnalyze,
handleGenerate,
handleRegenerate,
backToInput,
backToTopics,
copyToClipboard,
} = useScriptLearning({ isOpen });
if (!isOpen) return null;
const wordCountNum = Number(wordCount);
const wordCountValid = Number.isInteger(wordCountNum)
&& wordCountNum >= WORD_COUNT_MIN
&& wordCountNum <= WORD_COUNT_MAX;
const canGenerate = !!analysisId && !!selectedTopic && wordCountValid;
const handleApplyAndClose = () => {
if (!generatedScript.trim()) return;
onApply?.(generatedScript);
onClose();
};
return (
<AppModal
isOpen={isOpen}
onClose={onClose}
panelClassName="w-full max-w-2xl max-h-[90vh] rounded-2xl border border-white/10 bg-[#171821]/95 shadow-[0_24px_80px_rgba(0,0,0,0.55)] overflow-hidden flex flex-col"
closeOnOverlay={false}
closeOnEsc={false}
>
<AppModalHeader
title="文案深度学习"
icon={<BookOpen className="h-5 w-5 text-cyan-300" />}
subtitle="分析博主近期选题风格并快速生成文案"
onClose={onClose}
/>
<div className="flex-1 overflow-y-auto p-6">
{step === "input" && (
<div className="space-y-5">
<div className="space-y-2">
<label className="text-sm text-gray-300"></label>
<input
type="text"
value={inputUrl}
onChange={(event) => setInputUrl(event.target.value)}
placeholder="请粘贴抖音或B站博主主页链接..."
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-cyan-500 transition-colors"
/>
<p className="text-xs text-gray-500"> https 使</p>
</div>
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-4">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
<div className="flex gap-3 pt-1">
<button
type="button"
onClick={() => setInputUrl("")}
className="flex-1 py-3 px-4 bg-white/10 hover:bg-white/20 text-white rounded-xl transition-colors"
>
</button>
<button
type="button"
onClick={() => void handleAnalyze()}
disabled={!inputUrl.trim()}
className="flex-1 py-3 px-4 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-500 hover:to-cyan-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl transition-all font-medium shadow-lg"
>
</button>
</div>
</div>
)}
{(step === "analyzing" || step === "generating") && (
<div className="flex flex-col items-center justify-center py-20">
<div className="relative w-20 h-20 mb-6">
<div className="absolute inset-0 border-4 border-cyan-500/30 rounded-full" />
<div className="absolute inset-0 border-4 border-t-cyan-500 rounded-full animate-spin" />
</div>
<h4 className="text-xl font-medium text-white mb-2">
{step === "analyzing" ? "正在分析中..." : "正在生成中..."}
</h4>
</div>
)}
{step === "topics" && (
<div className="space-y-5">
<div className="bg-cyan-500/10 border border-cyan-500/30 rounded-xl p-3">
<p className="text-cyan-200 text-sm"></p>
</div>
<div className="space-y-2">
<p className="text-sm text-gray-300"></p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{topics.map((topic) => {
const active = selectedTopic === topic;
return (
<button
key={topic}
type="button"
onClick={() => setSelectedTopic(topic)}
className={`text-left rounded-lg border px-3 py-2.5 text-sm transition-colors ${
active
? "border-cyan-400 bg-cyan-500/20 text-cyan-100"
: "border-white/10 bg-white/5 text-gray-200 hover:border-white/20 hover:bg-white/10"
}`}
>
{topic}
</button>
);
})}
</div>
</div>
<div className="space-y-2">
<label className="text-sm text-gray-300"></label>
<input
type="number"
min={WORD_COUNT_MIN}
max={WORD_COUNT_MAX}
value={wordCount}
onChange={(event) => setWordCount(event.target.value)}
placeholder="请输入目标字数80-1000如 300"
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-cyan-500 transition-colors"
/>
</div>
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-4">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
<div className="flex gap-3 pt-1">
<button
type="button"
onClick={backToInput}
className="flex-1 py-3 px-4 bg-white/10 hover:bg-white/20 text-white rounded-xl transition-colors"
>
</button>
<button
type="button"
onClick={() => void handleGenerate()}
disabled={!canGenerate}
className="flex-1 py-3 px-4 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-500 hover:to-cyan-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl transition-all font-medium shadow-lg"
>
</button>
</div>
</div>
)}
{step === "result" && (
<div className="space-y-5">
<div className="flex justify-between items-center">
<h4 className="font-semibold text-cyan-200 flex items-center gap-2">
<Sparkles className="h-4 w-4" />
</h4>
<span className="text-xs text-gray-400">{generatedScript.length} </span>
</div>
<div className="bg-white/5 border border-white/10 rounded-xl p-4 max-h-72 overflow-y-auto hide-scrollbar">
<p className="text-gray-200 text-sm leading-relaxed whitespace-pre-wrap">{generatedScript}</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
<button
type="button"
onClick={handleApplyAndClose}
className="py-2.5 px-3 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-500 hover:to-cyan-500 text-white rounded-lg transition-colors text-sm"
>
</button>
<button
type="button"
onClick={() => copyToClipboard(generatedScript)}
className="py-2.5 px-3 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors text-sm"
>
</button>
<button
type="button"
onClick={() => void handleRegenerate()}
className="py-2.5 px-3 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors text-sm"
>
</button>
<button
type="button"
onClick={backToTopics}
className="py-2.5 px-3 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors text-sm"
>
</button>
</div>
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-4">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
</div>
)}
</div>
</AppModal>
);
}