forked from 77media/video-flow
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
2bf142f664
@ -62,8 +62,34 @@ export interface ScriptEpisode {
|
||||
}
|
||||
|
||||
// 新-获取剧集列表
|
||||
export const getScriptEpisodeListNew = async (data: any): Promise<ApiResponse<any>> => {
|
||||
return post<ApiResponse<any>>('/movie/list_movie_projects', data);
|
||||
interface ListMovieProjectsParams {
|
||||
user_id: string;
|
||||
page: number;
|
||||
per_page: number;
|
||||
}
|
||||
|
||||
interface MovieProject {
|
||||
project_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
step: string;
|
||||
final_video_url: string;
|
||||
final_simple_video_url: string;
|
||||
last_message: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface ListMovieProjectsResponse {
|
||||
movie_projects: MovieProject[];
|
||||
total: number;
|
||||
page: number;
|
||||
per_page: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export const getScriptEpisodeListNew = async (params: ListMovieProjectsParams): Promise<ApiResponse<ListMovieProjectsResponse>> => {
|
||||
return post<ApiResponse<ListMovieProjectsResponse>>('/movie/list_movie_projects', params);
|
||||
};
|
||||
|
||||
// 获取剧集详情
|
||||
|
||||
@ -22,13 +22,13 @@ export function ActionButton({
|
||||
<div className={`relative group ${className}`}>
|
||||
<div
|
||||
data-alt="action-button"
|
||||
className={`relative ${width} ${height} opacity-90 cursor-pointer overflow-hidden rounded-xl bg-black z-10`}
|
||||
className={`relative ${width} ${height} opacity-90 cursor-pointer overflow-hidden rounded-full bg-black z-10`}
|
||||
>
|
||||
<div className={`absolute z-10 -translate-x-12 group-hover:translate-x-12 transition-all duration-700 h-full ${width} bg-gradient-to-r from-gray-500 to-white/10 opacity-30 -skew-x-12`}></div>
|
||||
<div className="absolute flex items-center justify-center text-white z-[1] opacity-90 rounded-xl inset-0.5 bg-black">
|
||||
<div className="absolute flex items-center justify-center text-white z-[1] opacity-90 rounded-full inset-0.5 bg-black">
|
||||
<button
|
||||
name="text"
|
||||
className="w-full h-full opacity-90 rounded-xl bg-black flex items-center justify-center disabled:cursor-not-allowed"
|
||||
className="w-full h-full opacity-90 rounded-full bg-black flex items-center justify-center disabled:cursor-not-allowed"
|
||||
onClick={isCreating ? undefined : handleCreateVideo}
|
||||
disabled={isCreating || disabled}
|
||||
>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { ArrowLeft, ListOrdered, Play, Loader2, Pause, MoreHorizontal, Edit2, Check, X, RefreshCw, Calendar, Clock, Eye, Heart, Share2, Video, Pencil, Trash } from 'lucide-react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Loader2, Check, Pencil, Trash } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import './style/create-to-video2.css';
|
||||
|
||||
import { getScriptEpisodeListNew } from "@/api/script_episode";
|
||||
import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox';
|
||||
import cover_image1 from '@/public/assets/cover_image1.jpg';
|
||||
import cover_image1 from '@/public/assets/cover_image3.jpg';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
|
||||
@ -16,62 +16,95 @@ import { Tooltip, Button } from 'antd';
|
||||
|
||||
export default function CreateToVideo2() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const projectId = searchParams.get('projectId') ? parseInt(searchParams.get('projectId')!) : 0;
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [runTour, setRunTour] = useState(true);
|
||||
const [episodeId, setEpisodeId] = useState<number>(0);
|
||||
const [generatedVideoList, setGeneratedVideoList] = useState<any[]>([]);
|
||||
const [projectName, setProjectName] = useState('默认名称');
|
||||
const [episodeList, setEpisodeList] = useState<any[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPage, setTotalPage] = useState(1);
|
||||
const [limit, setLimit] = useState(12);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [perPage] = useState(12);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [userId, setUserId] = useState<number>(0);
|
||||
|
||||
// 添加一个 ref 来跟踪当前正在加载的页码
|
||||
const loadingPageRef = useRef<number | null>(null);
|
||||
|
||||
// 在客户端挂载后读取localStorage
|
||||
// 监听滚动事件,实现无限加载
|
||||
// 修改滚动处理函数,添加节流
|
||||
const handleScroll = useCallback(() => {
|
||||
if (!scrollContainerRef.current || !hasMore || isLoadingMore || isLoading) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
|
||||
if (scrollHeight - scrollTop - clientHeight < 100) {
|
||||
// 直接使用 currentPage,不再使用 setCurrentPage 的回调
|
||||
const nextPage = currentPage + 1;
|
||||
if (nextPage <= totalPages) {
|
||||
getEpisodeList(userId, nextPage, true);
|
||||
}
|
||||
}
|
||||
}, [hasMore, isLoadingMore, isLoading, totalPages, userId, currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
||||
console.log('currentUser', currentUser);
|
||||
setUserId(currentUser.id);
|
||||
const savedProjectName = localStorage.getItem('projectName');
|
||||
if (savedProjectName) {
|
||||
setProjectName(savedProjectName);
|
||||
}
|
||||
getEpisodeList(currentUser.id);
|
||||
getEpisodeList(currentUser.id, 1, false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 获取剧集列表
|
||||
const getEpisodeList = async (userId: number) => {
|
||||
if (isLoading || isLoadingMore) return;
|
||||
console.log('getEpisodeList', userId);
|
||||
// 添加滚动监听
|
||||
useEffect(() => {
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
if (scrollContainer) {
|
||||
scrollContainer.addEventListener('scroll', handleScroll);
|
||||
return () => scrollContainer.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
}, [handleScroll]);
|
||||
|
||||
setIsLoading(true);
|
||||
// 修改获取剧集列表函数
|
||||
const getEpisodeList = async (userId: number, page: number = 1, loadMore: boolean = false) => {
|
||||
// 检查是否正在加载该页
|
||||
if (loadingPageRef.current === page) return;
|
||||
if (isLoading || (isLoadingMore && !loadMore)) return;
|
||||
|
||||
// 设置当前正在加载的页码
|
||||
loadingPageRef.current = page;
|
||||
|
||||
if (loadMore) {
|
||||
setIsLoadingMore(true);
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const params = {
|
||||
user_id: String(userId),
|
||||
page,
|
||||
per_page: perPage
|
||||
};
|
||||
|
||||
const episodeListResponse = await getScriptEpisodeListNew(params);
|
||||
console.log('episodeListResponse', episodeListResponse);
|
||||
|
||||
if (episodeListResponse.code === 0) {
|
||||
setEpisodeList(episodeListResponse.data.movie_projects);
|
||||
// 每一项 有
|
||||
// final_video_url: "", // 生成的视频地址
|
||||
// last_message: "",
|
||||
// name: "After the Flood", // 剧集名称
|
||||
// project_id: "9c34fcc4-c8d8-44fc-879e-9bd56f608c76", // 剧集ID
|
||||
// status: "INIT", // 剧集状态 INIT 初始化
|
||||
// step: "INIT" // 剧集步骤 INIT 初始化
|
||||
const { movie_projects, total_pages } = episodeListResponse.data;
|
||||
|
||||
// 确保数据不重复
|
||||
if (loadMore) {
|
||||
setEpisodeList(prev => {
|
||||
const newProjects = movie_projects.filter(
|
||||
project => !prev.some(p => p.project_id === project.project_id)
|
||||
);
|
||||
return [...prev, ...newProjects];
|
||||
});
|
||||
} else {
|
||||
setEpisodeList(movie_projects);
|
||||
}
|
||||
|
||||
setTotalPages(total_pages);
|
||||
setHasMore(page < total_pages);
|
||||
setCurrentPage(page);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@ -79,6 +112,8 @@ export default function CreateToVideo2() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsLoadingMore(false);
|
||||
// 清除当前加载页码
|
||||
loadingPageRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -86,20 +121,6 @@ export default function CreateToVideo2() {
|
||||
|
||||
// 所有视频工具相关的函数和配置已迁移到ChatInputBox组件中
|
||||
|
||||
// 检查是否需要显示引导
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const hasCompletedTour = localStorage.getItem('hasCompletedTour');
|
||||
if (hasCompletedTour) {
|
||||
setRunTour(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const StatusBadge = (status: string) => {
|
||||
return (
|
||||
<motion.div
|
||||
@ -176,16 +197,16 @@ export default function CreateToVideo2() {
|
||||
>
|
||||
{/* 视频/图片区域 */}
|
||||
<div className="relative aspect-video">
|
||||
{project.final_video_url ? (
|
||||
{(project.final_video_url || project.final_simple_video_url) ? (
|
||||
<video
|
||||
ref={(el) => setVideoRef(project.project_id, el)}
|
||||
src={project.final_video_url}
|
||||
src={project.final_video_url || project.final_simple_video_url}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={`${project.final_video_url}?vframe/jpg/offset/1`}
|
||||
poster={`${project.final_video_url || project.final_simple_video_url}?vframe/jpg/offset/1`}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@ -198,7 +219,7 @@ export default function CreateToVideo2() {
|
||||
|
||||
{/* 状态标签 - 左上角 */}
|
||||
<div className="absolute top-3 left-3">
|
||||
{StatusBadge(project.status === 'COMPLETED' ? 'completed' : project.status === 'FAILED' ? 'failed' : 'pending')}
|
||||
{StatusBadge((project.status === 'COMPLETED' || project.final_simple_video_url) ? 'completed' : project.status === 'FAILED' ? 'failed' : 'pending')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -250,18 +271,6 @@ export default function CreateToVideo2() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 到底提示 */}
|
||||
{!hasMore && episodeList.length > 0 && (
|
||||
<div className="flex justify-center py-12">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-black/30 backdrop-blur-xl border border-white/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Check className="w-6 h-6 text-purple-400" />
|
||||
</div>
|
||||
<p className="text-white/70 text-sm">All projects loaded</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -161,7 +161,7 @@ export function useWorkflowData() {
|
||||
if (taskObject.scenes.total_count > realSketchResultData.length) {
|
||||
loadingText.current = LOADING_TEXT_MAP.sketch(realSketchResultData.length, taskObject.scenes.total_count);
|
||||
} else {
|
||||
loadingText.current = LOADING_TEXT_MAP.getShotSketchStatus;
|
||||
loadingText.current = LOADING_TEXT_MAP.getVideoStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,12 +209,6 @@ export function useWorkflowData() {
|
||||
|
||||
// 收集所有需要更新的状态
|
||||
let stateUpdates = JSON.stringify(taskCurrent);
|
||||
// 视频分析
|
||||
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||
setCanGoToCut(true);
|
||||
}
|
||||
|
||||
for (const task of all_task_data) {
|
||||
// 如果有已完成的数据,同步到状态
|
||||
@ -312,6 +306,13 @@ export function useWorkflowData() {
|
||||
}
|
||||
}
|
||||
|
||||
// 视频分析
|
||||
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||
setCanGoToCut(true);
|
||||
}
|
||||
|
||||
// 粗剪
|
||||
if (task.task_name === 'generate_final_simple_video') {
|
||||
if (task.task_result && task.task_result.video) {
|
||||
|
||||
BIN
public/assets/cover_image3.jpg
Normal file
BIN
public/assets/cover_image3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 890 KiB |
Loading…
x
Reference in New Issue
Block a user