- [x] Chatbox-快捷操作按钮

This commit is contained in:
北枳 2025-08-30 20:30:20 +08:00
parent 1e4be3b1a0
commit 684f29056e
4 changed files with 140 additions and 2 deletions

View File

@ -3,6 +3,7 @@ import { Image as ImageIcon, Send, Trash2, ArrowUp } from "lucide-react";
import { MessageBlock } from "./types";
import { useUploadFile } from "@/app/service/domain/service";
import { motion, AnimatePresence } from "framer-motion";
import { QuickActionTags, QuickAction } from "./QuickActionTags";
// 防抖函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number) {
@ -233,6 +234,19 @@ export function InputBar({ onSend, setVideoPreview, initialVideoUrl, initialVide
</div>
)}
{/* 快捷操作标签组 */}
<QuickActionTags
onTagClick={(action: QuickAction) => {
// 将标签文本添加到输入框
setText(action.label);
// 聚焦输入框并触发高度调整
if (textareaRef.current) {
textareaRef.current.focus();
adjustHeight();
}
}}
/>
<motion.div
layout
className="px-3 m-3 border border-gray-700 rounded-[2rem]"

View File

@ -0,0 +1,123 @@
import React, { useRef, useCallback } from 'react';
import { motion } from 'framer-motion';
import { ChevronLeft, ChevronRight } from 'lucide-react';
/** 快捷操作标签的数据结构 */
export interface QuickAction {
id: string;
label: string;
}
/** 预设的快捷操作标签 */
export const DEFAULT_QUICK_ACTIONS: QuickAction[] = [
{ id: 'weather', label: 'Change video scene weather' },
{ id: 'character', label: 'Change a character in the video' },
{ id: 'costume', label: 'Change the clothing of a character in the video' },
{ id: 'scene', label: 'Change video scene background' },
{ id: 'action', label: 'Change character action' }
];
interface QuickActionTagsProps {
/** 自定义标签列表,如果不提供则使用默认标签 */
actions?: QuickAction[];
/** 点击标签时的回调函数 */
onTagClick: (action: QuickAction) => void;
}
/**
*
* @param props
* @returns JSX.Element
*/
export function QuickActionTags({ actions = DEFAULT_QUICK_ACTIONS, onTagClick }: QuickActionTagsProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const scroll = useCallback((direction: 'left' | 'right') => {
const container = scrollContainerRef.current;
if (!container) return;
const scrollAmount = 200; // 每次滚动的距离
const targetScroll = container.scrollLeft + (direction === 'left' ? -scrollAmount : scrollAmount);
container.scrollTo({
left: targetScroll,
behavior: 'smooth'
});
}, []);
return (
<div
data-alt="quick-action-tags"
className="relative flex items-center px-3 py-2 group"
>
{/* 左侧渐变遮罩 */}
<div className="absolute left-0 top-0 bottom-0 w-12 bg-gradient-to-r from-black/20 to-transparent pointer-events-none z-10" />
{/* 左滚动按钮 */}
<motion.button
onClick={() => scroll('left')}
className="absolute left-1 z-20 p-1 rounded-full bg-black/30 text-white/80
backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity
hover:bg-black/40"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
data-alt="scroll-left"
>
<ChevronLeft size={16} />
</motion.button>
{/* 标签滚动容器 */}
<div
ref={scrollContainerRef}
className="flex overflow-x-auto gap-2 no-scrollbar scroll-smooth"
style={{
msOverflowStyle: 'none', /* IE and Edge */
scrollbarWidth: 'none', /* Firefox */
}}
>
{actions.map((action) => (
<motion.button
key={action.id}
onClick={() => onTagClick(action)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex-none px-[8px] py-[3px] rounded-full text-[10px] text-white/80
backdrop-blur-md bg-white/10 border border-white/20
hover:bg-white/20 hover:text-white
transition-colors duration-200
shadow-[0_4px_6px_-1px_rgba(0,0,0,0.1),0_2px_4px_-1px_rgba(0,0,0,0.06)]"
data-alt={`quick-action-${action.id}`}
>
{action.label}
</motion.button>
))}
</div>
{/* 右侧渐变遮罩 */}
<div className="absolute right-0 top-0 bottom-0 w-12 bg-gradient-to-l from-black/20 to-transparent pointer-events-none z-10" />
{/* 右滚动按钮 */}
<motion.button
onClick={() => scroll('right')}
className="absolute right-1 z-20 p-1 rounded-full bg-black/30 text-white/80
backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity
hover:bg-black/40"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
data-alt="scroll-right"
>
<ChevronRight size={16} />
</motion.button>
</div>
);
}
// 添加全局样式来隐藏滚动条
const style = document.createElement('style');
style.textContent = `
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
`;
document.head.appendChild(style);

View File

@ -168,7 +168,7 @@ export default function SmartChatBox({
</div>
{/* Loading indicator */}
{isLoading && !hasMore && (
{isLoading && (
<div className="flex justify-start space-x-1 p-2">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.15s]"></span>

View File

@ -17,6 +17,7 @@ export function useWorkflowData() {
const episodeId = searchParams.get('episodeId') || '';
const from = searchParams.get('from') || '';
const token = localStorage.getItem('token') || '';
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
let tempTaskObject = useRef<TaskObject>({
title: '',
@ -114,7 +115,7 @@ export function useWorkflowData() {
const generateEditPlan = useCallback(async () => {
await getGenerateEditPlan({ project_id: episodeId });
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}`, '_self');
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}&userid=${useid}`, '_self');
}, [episodeId]);
// useEffect(() => {