forked from 77media/video-flow
220 lines
7.7 KiB
TypeScript
220 lines
7.7 KiB
TypeScript
import { useState, useRef, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { Sparkles, Send, X, Lightbulb, ChevronUp } from 'lucide-react';
|
|
import { GlassIconButton } from "@/components/ui/glass-icon-button";
|
|
|
|
interface AISuggestionBarProps {
|
|
suggestions: string[];
|
|
onSuggestionClick: (suggestion: string) => void;
|
|
onSubmit: (text: string) => void;
|
|
placeholder?: string;
|
|
}
|
|
|
|
export function AISuggestionBar({
|
|
suggestions,
|
|
onSuggestionClick,
|
|
onSubmit,
|
|
placeholder = "输入你的想法,或点击预设词条获取 AI 建议..."
|
|
}: AISuggestionBarProps) {
|
|
const [inputText, setInputText] = useState('');
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
// 自动调整输入框高度
|
|
useEffect(() => {
|
|
if (inputRef.current) {
|
|
inputRef.current.style.height = 'auto';
|
|
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`;
|
|
}
|
|
}, [inputText]);
|
|
|
|
// 处理提交
|
|
const handleSubmit = () => {
|
|
if (inputText.trim()) {
|
|
onSubmit(inputText.trim());
|
|
setInputText('');
|
|
if (inputRef.current) {
|
|
inputRef.current.style.height = 'auto';
|
|
}
|
|
}
|
|
};
|
|
|
|
// 处理回车提交
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSubmit();
|
|
}
|
|
};
|
|
|
|
// 切换折叠状态
|
|
const toggleCollapse = () => {
|
|
setIsCollapsed(!isCollapsed);
|
|
if (isCollapsed) {
|
|
// 展开时自动显示建议
|
|
setShowSuggestions(true);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ y: 100, opacity: 0 }}
|
|
animate={{
|
|
y: isCollapsed ? 'calc(100% - 10px)' : 0,
|
|
opacity: 1,
|
|
transition: {
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 30
|
|
}
|
|
}}
|
|
className="fixed bottom-0 left-0 right-0 z-50 bg-gradient-to-t from-[#0C0E11] via-[#0C0E11] to-transparent pb-8"
|
|
>
|
|
{/* 折叠/展开按钮 */}
|
|
<div className="absolute -top-[1rem] left-1/2 -translate-x-1/2" style={{ zIndex: 9 }}>
|
|
<motion.div
|
|
animate={{ rotate: isCollapsed ? 180 : 0 }}
|
|
transition={{ type: "spring", stiffness: 200, damping: 20 }}
|
|
>
|
|
<GlassIconButton
|
|
icon={ChevronUp}
|
|
size='sm'
|
|
tooltip={isCollapsed ? "展开" : "收起"}
|
|
onClick={toggleCollapse}
|
|
/>
|
|
</motion.div>
|
|
</div>
|
|
|
|
<div className="max-w-5xl mx-auto px-6">
|
|
{/* 智能预设词条 英文 */}
|
|
<AnimatePresence>
|
|
{showSuggestions && !isCollapsed && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
transition={{
|
|
height: { type: "spring", stiffness: 300, damping: 30 },
|
|
opacity: { duration: 0.2 }
|
|
}}
|
|
className="mb-4 pt-4 px-4 overflow-hidden bg-black/40 rounded-xl backdrop-blur-sm"
|
|
>
|
|
<motion.div
|
|
className="flex items-center gap-3 mb-3"
|
|
initial={{ x: -20, opacity: 0 }}
|
|
animate={{ x: 0, opacity: 1 }}
|
|
transition={{ delay: 0.1 }}
|
|
>
|
|
<motion.div
|
|
animate={{
|
|
rotate: [0, 15, -15, 0],
|
|
scale: [1, 1.2, 1.2, 1]
|
|
}}
|
|
transition={{
|
|
duration: 1,
|
|
repeat: Infinity,
|
|
repeatDelay: 2
|
|
}}
|
|
>
|
|
<Lightbulb className="w-4 h-4 text-yellow-500" />
|
|
</motion.div>
|
|
<span className="text-sm text-white/60">Smart preset tags</span>
|
|
</motion.div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{suggestions.map((suggestion, index) => (
|
|
<motion.button
|
|
key={suggestion}
|
|
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
|
animate={{
|
|
opacity: 1,
|
|
scale: 1,
|
|
y: 0,
|
|
transition: {
|
|
delay: index * 0.1,
|
|
type: "spring",
|
|
stiffness: 400,
|
|
damping: 25
|
|
}
|
|
}}
|
|
whileHover={{
|
|
scale: 1.05,
|
|
backgroundColor: "rgba(255, 255, 255, 0.15)"
|
|
}}
|
|
whileTap={{ scale: 0.95 }}
|
|
className="px-3 py-1.5 rounded-full bg-white/5 hover:bg-white/10 backdrop-blur-sm
|
|
text-sm text-white/70 hover:text-white transition-colors flex items-center gap-2"
|
|
onClick={() => onSuggestionClick(suggestion)}
|
|
>
|
|
<Sparkles className="w-3 h-3" />
|
|
{suggestion}
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* 输入区域 */}
|
|
<motion.div
|
|
className={`
|
|
relative rounded-xl bg-white/5 backdrop-blur-sm transition-all duration-300
|
|
${isFocused ? 'ring-2 ring-blue-500/50 bg-white/10' : 'hover:bg-white/[0.07]'}
|
|
${isCollapsed ? 'opacity-50 hover:opacity-100' : ''}
|
|
`}
|
|
whileHover={isCollapsed ? { scale: 1.02 } : {}}
|
|
onClick={() => isCollapsed && toggleCollapse()}
|
|
>
|
|
<textarea
|
|
ref={inputRef}
|
|
value={inputText}
|
|
onChange={(e) => setInputText(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
onFocus={() => {
|
|
setIsFocused(true);
|
|
setShowSuggestions(true);
|
|
if (isCollapsed) {
|
|
toggleCollapse();
|
|
}
|
|
}}
|
|
onBlur={() => setIsFocused(false)}
|
|
placeholder={isCollapsed ? "点击展开..." : placeholder}
|
|
className="w-full resize-none bg-transparent border-none px-4 py-3 text-white placeholder:text-white/40
|
|
focus:outline-none min-h-[52px] max-h-[150px] pr-[100px]"
|
|
rows={1}
|
|
disabled={isCollapsed}
|
|
/>
|
|
|
|
{/* 操作按钮 */}
|
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2">
|
|
<motion.button
|
|
className={`
|
|
p-2 rounded-lg transition-colors
|
|
${showSuggestions ? 'bg-white/10 text-white' : 'text-white/40 hover:text-white/60'}
|
|
`}
|
|
onClick={() => !isCollapsed && setShowSuggestions(!showSuggestions)}
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
disabled={isCollapsed}
|
|
>
|
|
{showSuggestions ? <X className="w-5 h-5" /> : <Sparkles className="w-5 h-5" />}
|
|
</motion.button>
|
|
<motion.button
|
|
className={`
|
|
p-2 rounded-lg transition-colors
|
|
${inputText.trim() ? 'bg-blue-500 text-white' : 'bg-white/5 text-white/20'}
|
|
`}
|
|
onClick={handleSubmit}
|
|
disabled={!inputText.trim() || isCollapsed}
|
|
whileHover={inputText.trim() ? { scale: 1.1 } : {}}
|
|
whileTap={inputText.trim() ? { scale: 0.9 } : {}}
|
|
>
|
|
<Send className="w-5 h-5" />
|
|
</motion.button>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|