forked from 77media/video-flow
chatbox 加入 添加分镜视频
This commit is contained in:
parent
fef7e0ed26
commit
8a0474fbf8
@ -27,6 +27,7 @@ import { AudioRecorder } from "./AudioRecorder";
|
|||||||
import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService";
|
import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { createMovieProjectV1 } from "@/api/video_flow";
|
import { createMovieProjectV1 } from "@/api/video_flow";
|
||||||
|
import { createScriptEpisodeNew } from "@/api/script_episode";
|
||||||
import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service";
|
import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service";
|
||||||
import { ActionButton } from "../common/ActionButton";
|
import { ActionButton } from "../common/ActionButton";
|
||||||
import { HighlightEditor } from "../common/HighlightEditor";
|
import { HighlightEditor } from "../common/HighlightEditor";
|
||||||
@ -608,7 +609,7 @@ export function ChatInputBox() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 调用创建剧集API
|
// 调用创建剧集API
|
||||||
const episodeResponse = await createMovieProjectV1(episodeData);
|
const episodeResponse = await createScriptEpisodeNew(episodeData);
|
||||||
console.log("episodeResponse", episodeResponse);
|
console.log("episodeResponse", episodeResponse);
|
||||||
if (episodeResponse.code !== 0) {
|
if (episodeResponse.code !== 0) {
|
||||||
console.error(`创建剧集失败: ${episodeResponse.message}`);
|
console.error(`创建剧集失败: ${episodeResponse.message}`);
|
||||||
|
|||||||
@ -1,29 +1,48 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
import { Image as ImageIcon, Send, Trash2 } from "lucide-react";
|
import { Image as ImageIcon, Send, Trash2 } from "lucide-react";
|
||||||
import { MessageBlock } from "./types";
|
import { MessageBlock } from "./types";
|
||||||
import { useUploadFile } from "@/app/service/domain/service";
|
import { useUploadFile } from "@/app/service/domain/service";
|
||||||
|
|
||||||
interface InputBarProps {
|
interface InputBarProps {
|
||||||
onSend: (blocks: MessageBlock[]) => void;
|
onSend: (blocks: MessageBlock[], videoId?: string) => void;
|
||||||
|
setVideoPreview?: (url: string, id: string) => void;
|
||||||
|
initialVideoUrl?: string;
|
||||||
|
initialVideoId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputBar({ onSend }: InputBarProps) {
|
export function InputBar({ onSend, setVideoPreview, initialVideoUrl, initialVideoId }: InputBarProps) {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
|
const [videoUrl, setVideoUrl] = useState<string | null>(initialVideoUrl || null);
|
||||||
|
const [videoId, setVideoId] = useState<string | null>(initialVideoId || null);
|
||||||
|
|
||||||
const { uploadFile } = useUploadFile();
|
const { uploadFile } = useUploadFile();
|
||||||
|
|
||||||
|
// 监听初始视频 URL 和 ID 的变化
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialVideoUrl && initialVideoId) {
|
||||||
|
setVideoUrl(initialVideoUrl);
|
||||||
|
setVideoId(initialVideoId);
|
||||||
|
}
|
||||||
|
}, [initialVideoUrl, initialVideoId]);
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
const blocks: MessageBlock[] = [];
|
const blocks: MessageBlock[] = [];
|
||||||
if (text.trim()) blocks.push({ type: "text" as const, text: text.trim() });
|
if (text.trim()) blocks.push({ type: "text" as const, text: text.trim() });
|
||||||
if (imageUrl) blocks.push({ type: "image" as const, url: imageUrl });
|
if (imageUrl) blocks.push({ type: "image" as const, url: imageUrl });
|
||||||
|
if (videoUrl) blocks.push({ type: "video" as const, url: videoUrl });
|
||||||
if (!blocks.length) return;
|
if (!blocks.length) return;
|
||||||
|
|
||||||
onSend(blocks);
|
onSend(blocks, videoId || undefined);
|
||||||
setText("");
|
setText("");
|
||||||
setImageUrl(null);
|
setImageUrl(null);
|
||||||
|
if (videoUrl && videoId && setVideoPreview) {
|
||||||
|
setVideoPreview(videoUrl, videoId);
|
||||||
|
}
|
||||||
|
setVideoUrl(null);
|
||||||
|
setVideoId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileUpload = async (file: File) => {
|
const handleFileUpload = async (file: File) => {
|
||||||
@ -32,7 +51,17 @@ export function InputBar({ onSend }: InputBarProps) {
|
|||||||
const url = await uploadFile(file, (progress) => {
|
const url = await uploadFile(file, (progress) => {
|
||||||
setUploadProgress(progress);
|
setUploadProgress(progress);
|
||||||
});
|
});
|
||||||
|
// 如果已经有视频,先保存视频状态
|
||||||
|
const prevVideoUrl = videoUrl;
|
||||||
|
const prevVideoId = videoId;
|
||||||
|
|
||||||
setImageUrl(url);
|
setImageUrl(url);
|
||||||
|
|
||||||
|
// 恢复视频状态
|
||||||
|
if (prevVideoUrl && prevVideoId) {
|
||||||
|
setVideoUrl(prevVideoUrl);
|
||||||
|
setVideoId(prevVideoId);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("上传失败:", error);
|
console.error("上传失败:", error);
|
||||||
// 可以添加错误提示
|
// 可以添加错误提示
|
||||||
@ -65,9 +94,10 @@ export function InputBar({ onSend }: InputBarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-alt="input-bar">
|
<div data-alt="input-bar">
|
||||||
{/* 图片预览 */}
|
{/* 媒体预览 */}
|
||||||
{imageUrl && (
|
<div className="px-3 pt-3 flex gap-2" data-alt="media-preview">
|
||||||
<div className="px-3 pt-3" data-alt="image-preview">
|
{/* 图片预览 */}
|
||||||
|
{imageUrl && (
|
||||||
<div className="relative group w-24 h-24">
|
<div className="relative group w-24 h-24">
|
||||||
<img
|
<img
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
@ -83,8 +113,33 @@ export function InputBar({ onSend }: InputBarProps) {
|
|||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
{/* 视频预览 */}
|
||||||
|
{videoUrl && (
|
||||||
|
<div className="relative group w-24 h-24">
|
||||||
|
<video
|
||||||
|
src={videoUrl}
|
||||||
|
className="h-full w-full object-cover rounded-xl border border-white/10"
|
||||||
|
controls={false}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (setVideoPreview) {
|
||||||
|
setVideoPreview(videoUrl!, videoId!);
|
||||||
|
}
|
||||||
|
setVideoUrl(null);
|
||||||
|
setVideoId(null);
|
||||||
|
}}
|
||||||
|
className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition bg-black/60 text-white rounded-full p-1"
|
||||||
|
title="移除"
|
||||||
|
data-alt="remove-video-button"
|
||||||
|
>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 上传进度 */}
|
{/* 上传进度 */}
|
||||||
{isUploading && (
|
{isUploading && (
|
||||||
@ -134,7 +189,7 @@ export function InputBar({ onSend }: InputBarProps) {
|
|||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
className="inline-flex items-center gap-2 p-2 my-2 rounded-full bg-blue-500 hover:bg-blue-400 text-white shadow disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"
|
className="inline-flex items-center gap-2 p-2 my-2 rounded-full bg-blue-500 hover:bg-blue-400 text-white shadow disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"
|
||||||
data-alt="send-button"
|
data-alt="send-button"
|
||||||
disabled={!text.trim() && !imageUrl}
|
disabled={!text.trim() && !imageUrl && !videoUrl}
|
||||||
>
|
>
|
||||||
<Send size={18} />
|
<Send size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -13,6 +13,9 @@ interface SmartChatBoxProps {
|
|||||||
setIsSmartChatBoxOpen: (v: boolean) => void;
|
setIsSmartChatBoxOpen: (v: boolean) => void;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
previewVideoUrl?: string | null;
|
||||||
|
previewVideoId?: string | null;
|
||||||
|
onClearPreview?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageGroup {
|
interface MessageGroup {
|
||||||
@ -32,7 +35,15 @@ function BackToLatestButton({ onClick }: { onClick: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen, projectId, userId }: SmartChatBoxProps) {
|
export default function SmartChatBox({
|
||||||
|
isSmartChatBoxOpen,
|
||||||
|
setIsSmartChatBoxOpen,
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
previewVideoUrl,
|
||||||
|
previewVideoId,
|
||||||
|
onClearPreview
|
||||||
|
}: SmartChatBoxProps) {
|
||||||
// 消息列表引用
|
// 消息列表引用
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||||
@ -52,6 +63,11 @@ export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen
|
|||||||
checkIfAtBottom();
|
checkIfAtBottom();
|
||||||
}, [checkIfAtBottom]);
|
}, [checkIfAtBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('previewVideoUrl', previewVideoUrl);
|
||||||
|
console.log('previewVideoId', previewVideoId);
|
||||||
|
}, [previewVideoUrl, previewVideoId]);
|
||||||
|
|
||||||
// 监听滚动事件
|
// 监听滚动事件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listElement = listRef.current;
|
const listElement = listRef.current;
|
||||||
@ -170,7 +186,16 @@ export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Input */}
|
{/* Input */}
|
||||||
<InputBar onSend={sendMessage} />
|
<InputBar
|
||||||
|
onSend={sendMessage}
|
||||||
|
setVideoPreview={(url, id) => {
|
||||||
|
if (url === previewVideoUrl && id === previewVideoId) {
|
||||||
|
onClearPreview?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
initialVideoUrl={previewVideoUrl || undefined}
|
||||||
|
initialVideoId={previewVideoId || undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -519,11 +519,13 @@ export async function fetchMessages(
|
|||||||
*/
|
*/
|
||||||
export async function sendMessage(
|
export async function sendMessage(
|
||||||
blocks: MessageBlock[],
|
blocks: MessageBlock[],
|
||||||
config: ChatConfig
|
config: ChatConfig,
|
||||||
|
videoId?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 提取文本和图片
|
// 提取文本、图片和视频
|
||||||
const textBlocks = blocks.filter(b => b.type === "text");
|
const textBlocks = blocks.filter(b => b.type === "text");
|
||||||
const imageBlocks = blocks.filter(b => b.type === "image");
|
const imageBlocks = blocks.filter(b => b.type === "image");
|
||||||
|
const videoBlocks = blocks.filter(b => b.type === "video");
|
||||||
|
|
||||||
const request: SendMessageRequest = {
|
const request: SendMessageRequest = {
|
||||||
session_id: `project_${config.projectId}_user_${config.userId}`,
|
session_id: `project_${config.projectId}_user_${config.userId}`,
|
||||||
@ -537,6 +539,16 @@ export async function sendMessage(
|
|||||||
request.image_url = (imageBlocks[0] as { url: string }).url;
|
request.image_url = (imageBlocks[0] as { url: string }).url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果有视频,添加视频URL
|
||||||
|
if (videoBlocks.length > 0) {
|
||||||
|
request.video_url = (videoBlocks[0] as { url: string }).url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有视频ID,添加到请求中
|
||||||
|
if (videoId) {
|
||||||
|
request.video_id = videoId;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('发送消息请求:', request);
|
console.log('发送消息请求:', request);
|
||||||
await post<ApiResponse<RealApiMessage>>("/intelligent/chat", request);
|
await post<ApiResponse<RealApiMessage>>("/intelligent/chat", request);
|
||||||
|
|||||||
@ -54,6 +54,8 @@ export interface SendMessageRequest {
|
|||||||
session_id: string;
|
session_id: string;
|
||||||
user_input: string;
|
user_input: string;
|
||||||
image_url?: string;
|
image_url?: string;
|
||||||
|
video_id?: string;
|
||||||
|
video_url?: string;
|
||||||
project_id: string;
|
project_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,7 +135,7 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
|
|||||||
}, [latestMessages, filterMessages, onMessagesUpdate]);
|
}, [latestMessages, filterMessages, onMessagesUpdate]);
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
const handleSendMessage = useCallback(async (blocks: MessageBlock[]) => {
|
const handleSendMessage = useCallback(async (blocks: MessageBlock[], videoId?: string) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 发送到服务器
|
// 发送到服务器
|
||||||
await sendMessage(blocks, configRef.current);
|
await sendMessage(blocks, configRef.current, videoId);
|
||||||
|
|
||||||
// 立即获取最新的消息列表
|
// 立即获取最新的消息列表
|
||||||
await updateMessages(false);
|
await updateMessages(false);
|
||||||
|
|||||||
@ -26,6 +26,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
|
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
|
||||||
const [activeEditTab, setActiveEditTab] = React.useState('1');
|
const [activeEditTab, setActiveEditTab] = React.useState('1');
|
||||||
const [isSmartChatBoxOpen, setIsSmartChatBoxOpen] = React.useState(true);
|
const [isSmartChatBoxOpen, setIsSmartChatBoxOpen] = React.useState(true);
|
||||||
|
const [previewVideoUrl, setPreviewVideoUrl] = React.useState<string | null>(null);
|
||||||
|
const [previewVideoId, setPreviewVideoId] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId') || '';
|
const episodeId = searchParams.get('episodeId') || '';
|
||||||
@ -145,6 +147,11 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
isPauseWorkFlow={isPauseWorkFlow}
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
applyScript={applyScript}
|
applyScript={applyScript}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
|
onOpenChat={() => setIsSmartChatBoxOpen(true)}
|
||||||
|
setVideoPreview={(url, id) => {
|
||||||
|
setPreviewVideoUrl(url);
|
||||||
|
setPreviewVideoId(id);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
@ -227,6 +234,12 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
setIsSmartChatBoxOpen={setIsSmartChatBoxOpen}
|
setIsSmartChatBoxOpen={setIsSmartChatBoxOpen}
|
||||||
projectId={episodeId}
|
projectId={episodeId}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
|
previewVideoUrl={previewVideoUrl}
|
||||||
|
previewVideoId={previewVideoId}
|
||||||
|
onClearPreview={() => {
|
||||||
|
setPreviewVideoUrl(null);
|
||||||
|
setPreviewVideoId(null);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
|||||||
import { mockScriptData } from '@/components/script-renderer/mock';
|
import { mockScriptData } from '@/components/script-renderer/mock';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import { Video } from 'lucide-react';
|
||||||
|
|
||||||
interface MediaViewerProps {
|
interface MediaViewerProps {
|
||||||
taskObject: TaskObject;
|
taskObject: TaskObject;
|
||||||
@ -22,6 +24,8 @@ interface MediaViewerProps {
|
|||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
applyScript: any;
|
applyScript: any;
|
||||||
mode: string;
|
mode: string;
|
||||||
|
onOpenChat?: () => void;
|
||||||
|
setVideoPreview?: (url: string, id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaViewer = React.memo(function MediaViewer({
|
export const MediaViewer = React.memo(function MediaViewer({
|
||||||
@ -35,7 +39,9 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
isPauseWorkFlow,
|
isPauseWorkFlow,
|
||||||
applyScript,
|
applyScript,
|
||||||
mode
|
mode,
|
||||||
|
onOpenChat,
|
||||||
|
setVideoPreview
|
||||||
}: MediaViewerProps) {
|
}: MediaViewerProps) {
|
||||||
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
@ -447,28 +453,47 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
|
|
||||||
{/* 视频 多个 取第一个 */}
|
{/* 视频 多个 取第一个 */}
|
||||||
{ taskObject.videos.data[currentSketchIndex].urls && (
|
{ taskObject.videos.data[currentSketchIndex].urls && (
|
||||||
<motion.div
|
<>
|
||||||
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
<motion.div
|
||||||
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
||||||
transition={{ duration: 0.8, ease: [0.43, 0.13, 0.23, 0.96] }}
|
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
||||||
className="relative z-10 w-full h-full"
|
transition={{ duration: 0.8, ease: [0.43, 0.13, 0.23, 0.96] }}
|
||||||
>
|
className="relative z-10 w-full h-full"
|
||||||
<video
|
>
|
||||||
ref={mainVideoRef}
|
<video
|
||||||
key={taskObject.videos.data[currentSketchIndex].urls[0]}
|
ref={mainVideoRef}
|
||||||
className="w-full h-full rounded-lg object-cover object-center relative z-10"
|
key={taskObject.videos.data[currentSketchIndex].urls[0]}
|
||||||
src={taskObject.videos.data[currentSketchIndex].urls[0]}
|
className="w-full h-full rounded-lg object-cover object-center relative z-10"
|
||||||
autoPlay={isVideoPlaying}
|
src={taskObject.videos.data[currentSketchIndex].urls[0]}
|
||||||
loop={true}
|
autoPlay={isVideoPlaying}
|
||||||
playsInline
|
loop={true}
|
||||||
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
playsInline
|
||||||
onEnded={() => {
|
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
||||||
if (isVideoPlaying) {
|
onEnded={() => {
|
||||||
// 自动切换到下一个视频的逻辑在父组件处理
|
if (isVideoPlaying) {
|
||||||
}
|
// 自动切换到下一个视频的逻辑在父组件处理
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
</motion.div>
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 添加到chat去编辑 按钮 */}
|
||||||
|
<Tooltip title="Add to chat to edit">
|
||||||
|
<Button
|
||||||
|
className="absolute top-4 left-4 z-[21] bg-white/10 backdrop-blur-sm border border-white/20 text-white"
|
||||||
|
onClick={() => {
|
||||||
|
const currentVideo = taskObject.videos.data[currentSketchIndex];
|
||||||
|
if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0 && setVideoPreview) {
|
||||||
|
setVideoPreview(currentVideo.urls[0], currentVideo.video_id);
|
||||||
|
if (onOpenChat) onOpenChat();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Video className="w-4 h-4" />
|
||||||
|
<span className="text-xs">Chat to edit</span>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 操作按钮组 */}
|
{/* 操作按钮组 */}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user