import { useState, useRef } from "react"; import { motion } from "framer-motion"; import { Button } from "@/components/ui/button"; import { Sparkles, X, Plus, Clock, MapPin, Sun, Moon, Cloud, CloudRain, CloudSnow, CloudLightning, Palette } from 'lucide-react'; import { cn } from "@/public/lib/utils"; import ContentEditable from 'react-contenteditable'; interface SceneAttribute { key: string; label: string; value: string; type: 'text' | 'number' | 'select'; options?: string[]; icon?: any; } interface SceneEnvironment { time: { period: '清晨' | '上午' | '中午' | '下午' | '傍晚' | '夜晚' | '深夜'; specific?: string; }; location: { main: string; detail?: string; }; weather: { type: '晴天' | '多云' | '雨天' | '雪天' | '雷暴' | '阴天'; description?: string; }; atmosphere: { lighting: string; mood: string; }; } interface SceneEditorProps { initialDescription?: string; onDescriptionChange?: (description: string) => void; onAttributesChange?: (attributes: SceneAttribute[]) => void; onEnvironmentChange?: (environment: SceneEnvironment) => void; className?: string; } // 天气图标映射 const weatherIcons = { '晴天': Sun, '多云': Cloud, '雨天': CloudRain, '雪天': CloudSnow, '雷暴': CloudLightning, '阴天': Cloud }; const mockParse = (text: string): { attributes: SceneAttribute[], environment: SceneEnvironment } => { // 模拟结构化解析结果 const attributes: SceneAttribute[] = []; const environment: SceneEnvironment = { time: { period: "下午", specific: "午后2点左右" }, location: { main: "教室", detail: "高中教室,靠窗的位置" }, weather: { type: "晴天", description: "阳光明媚,微风轻拂" }, atmosphere: { lighting: "自然光线充足,温暖的阳光从窗户斜射入室内", mood: "安静祥和,充满生机与活力" } }; // 添加环境属性到结构化标签 const environmentAttributes: SceneAttribute[] = [ { key: "time", label: "时间", value: `${environment.time.period}${environment.time.specific ? `,${environment.time.specific}` : ''}`, type: "text", icon: Clock }, { key: "location", label: "地点", value: `${environment.location.main}${environment.location.detail ? `,${environment.location.detail}` : ''}`, type: "text", icon: MapPin }, { key: "weather", label: "天气", value: `${environment.weather.type}${environment.weather.description ? `,${environment.weather.description}` : ''}`, type: "text", icon: weatherIcons[environment.weather.type as keyof typeof weatherIcons] }, { key: "atmosphere", label: "氛围", value: `${environment.atmosphere.lighting},${environment.atmosphere.mood}`, type: "text", icon: Palette } ]; return { attributes: [...attributes, ...environmentAttributes], environment }; }; export default function SceneEditor({ initialDescription = "一个银白短发的精灵女性,大约20岁,肤色白皙,身材高挑,身着白色连衣裙。在教室里,阳光透过窗户洒在地板上,营造出温暖而安静的氛围。", onDescriptionChange, onAttributesChange, onEnvironmentChange, className }: SceneEditorProps) { const [inputText, setInputText] = useState(initialDescription); const [isOptimizing, setIsOptimizing] = useState(false); const [customTags, setCustomTags] = useState([]); const [newTag, setNewTag] = useState(""); const parseResult = useRef(mockParse(initialDescription)); const contentEditableRef = useRef(null); const handleTextChange = (e: { target: { value: string } }) => { // 移除 HTML 标签,保留换行 const value = e.target.value; setInputText(value); onDescriptionChange?.(value); // 重新解析文本 const newParseResult = mockParse(value); parseResult.current = newParseResult; onAttributesChange?.(newParseResult.attributes); onEnvironmentChange?.(newParseResult.environment); }; // 格式化文本为 HTML const formatTextToHtml = (text: string) => { return text .split('\n') .map(line => line || '
') .join('
'); }; const handleSmartPolish = async () => { setIsOptimizing(true); try { const polishedText = "一位拥有银白短发、白皙肌肤的高挑精灵少女,年龄约二十岁,气质神秘优雅。在阳光明媚的午后,她站在教室靠窗的位置,温暖的阳光从窗户斜射入室内,为这个安静祥和的空间注入了生机与活力。"; setInputText(polishedText); const newParseResult = mockParse(polishedText); parseResult.current = newParseResult; onDescriptionChange?.(polishedText); onAttributesChange?.(newParseResult.attributes); onEnvironmentChange?.(newParseResult.environment); } finally { setIsOptimizing(false); } }; const handleAttributeChange = (attr: SceneAttribute, newValue: string) => { // 移除 HTML 标签 newValue = newValue.replace(/<[^>]*>/g, ''); // 更新描述文本 let newText = inputText; if (attr.type === "number" && attr.key === "age") { newText = newText.replace(/\d+岁/, `${newValue}岁`); } else { newText = newText.replace(new RegExp(attr.value, 'g'), newValue); } // 更新属性值 const newAttr = { ...attr, value: newValue }; const newAttributes = parseResult.current.attributes.map(a => a.key === attr.key ? newAttr : a ); // 重新解析文本以更新环境信息 const newParseResult = mockParse(newText); parseResult.current = newParseResult; setInputText(newText); onDescriptionChange?.(newText); onAttributesChange?.(newAttributes); onEnvironmentChange?.(newParseResult.environment); }; return (
{/* 自由输入区域 */}
{/* 智能润色按钮 */} {isOptimizing ? "优化中..." : "智能优化"}
{/* 结构化属性标签 */}
{parseResult.current.attributes.map((attr) => ( {attr.icon && } {attr.label}: handleAttributeChange(attr, e.target.value)} className="text-sm text-white/90 min-w-[1em] focus:outline-none border-b border-transparent focus:border-white/30 hover:border-white/20" onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); (e.target as HTMLElement).blur(); } }} /> ))}
{/* 自定义标签区域 */}
{customTags.map((tag) => ( {tag} ))}
setNewTag(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter' && newTag.trim()) { setCustomTags(tags => [...tags, newTag.trim()]); setInputText((text: string) => text + (text.endsWith("。") ? "" : ",") + newTag.trim()); setNewTag(""); } }} placeholder="添加自定义标签..." className="flex-1 px-3 py-2 bg-white/5 border-none rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 placeholder:text-white/30" />
); }