forked from 77media/video-flow
Merge branch 'dev' of https://git.qikongjian.com/77media/video-flow into dev
This commit is contained in:
commit
b543e28b23
@ -1,3 +1,4 @@
|
||||
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL
|
||||
// export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL
|
||||
export const BASE_URL = 'https://77.smartvideo.py.qikongjian.com'
|
||||
// export const BASE_URL ='http://192.168.120.5:8000'
|
||||
//
|
||||
|
||||
@ -183,6 +183,28 @@ export type ConvertScenePromptRequest =
|
||||
// 转换分镜头响应接口
|
||||
export type ConvertScenePromptResponse = BaseApiResponse<ScenePrompts>;
|
||||
|
||||
// 生成剪辑计划请求接口
|
||||
interface GenerateEditPlanRequest {
|
||||
project_id: string;
|
||||
}
|
||||
|
||||
interface GenerateEditPlanResponseData {
|
||||
project_id: string;
|
||||
director_intent: string;
|
||||
success: boolean;
|
||||
editing_plan: any;
|
||||
error: string | null;
|
||||
processing_time: number;
|
||||
video_count: number;
|
||||
from_cache: boolean;
|
||||
total_videos: number;
|
||||
analyzed_count: number;
|
||||
unanalyzed_videos: string[];
|
||||
}
|
||||
|
||||
// 生成剪辑计划响应接口
|
||||
export type GenerateEditPlanResponse = BaseApiResponse<GenerateEditPlanResponseData>;
|
||||
|
||||
/**
|
||||
* 将剧本或视频转换为分镜头提示词
|
||||
* @param request - 请求参数,根据 project_type 自动判断是剧本还是视频模式
|
||||
@ -269,6 +291,11 @@ export const getRunningStreamData = async (data: {
|
||||
return post<ApiResponse<any>>("/movie/get_status", data);
|
||||
};
|
||||
|
||||
// 获取 生成剪辑计划 接口
|
||||
export const getGenerateEditPlan = async (data: GenerateEditPlanRequest): Promise<ApiResponse<GenerateEditPlanResponse>> => {
|
||||
return post<ApiResponse<GenerateEditPlanResponse>>("/edit-plan/generate-by-project", data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取项目任务列表接口
|
||||
* @param data - 请求参数
|
||||
|
||||
@ -39,7 +39,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<title>Movie Flow - Create Amazing Movies with AI</title>
|
||||
<title>MovieFlow - Create Amazing Movies with AI</title>
|
||||
<meta name="description" content="Professional AI-powered video creation platform with advanced editing tools" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
@ -111,7 +111,7 @@ export function MessageRenderer({ msg }: MessageRendererProps) {
|
||||
src={b.url}
|
||||
poster={b.poster}
|
||||
className="w-full max-h-80 bg-black"
|
||||
controlsList="nodownload noremoteplayback"
|
||||
controlsList="noremoteplayback"
|
||||
disablePictureInPicture
|
||||
disableRemotePlayback
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
|
||||
@ -133,8 +133,8 @@ export default function SmartChatBox({
|
||||
<span>Chat</span>
|
||||
{/* System push toggle */}
|
||||
<Switch
|
||||
checkedChildren="System push: On"
|
||||
unCheckedChildren="System push: Off"
|
||||
checkedChildren="System: On"
|
||||
unCheckedChildren="System: Off"
|
||||
checked={systemPush}
|
||||
onChange={toggleSystemPush}
|
||||
className="ml-2 "
|
||||
|
||||
@ -52,7 +52,9 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
setAnyAttribute,
|
||||
applyScript,
|
||||
fallbackToStep,
|
||||
originalText
|
||||
originalText,
|
||||
showGotoCutButton,
|
||||
generateEditPlan
|
||||
} = useWorkflowData();
|
||||
|
||||
const {
|
||||
@ -91,6 +93,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
currentLoadingText={currentLoadingText}
|
||||
roles={taskObject.roles.data}
|
||||
isPauseWorkFlow={isPauseWorkFlow}
|
||||
showGotoCutButton={showGotoCutButton}
|
||||
onGotoCut={generateEditPlan}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
@ -11,12 +11,16 @@ import {
|
||||
Scissors
|
||||
} from 'lucide-react';
|
||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
interface TaskInfoProps {
|
||||
taskObject: TaskObject;
|
||||
currentLoadingText: string;
|
||||
roles: any[];
|
||||
isPauseWorkFlow: boolean;
|
||||
showGotoCutButton: boolean;
|
||||
onGotoCut?: () => void;
|
||||
}
|
||||
|
||||
const stageIconMap = {
|
||||
@ -38,7 +42,7 @@ const stageIconMap = {
|
||||
}
|
||||
}
|
||||
|
||||
const TAG_COLORS = ['#126821', '#A133FF', '#3333FF', '#a1115e'];
|
||||
const TAG_COLORS = ['#A133FF', '#a1115e'];
|
||||
|
||||
// 阶段图标组件
|
||||
const StageIcons = ({ currentStage, isExpanded, isPauseWorkFlow }: { currentStage: number, isExpanded: boolean, isPauseWorkFlow: boolean }) => {
|
||||
@ -123,7 +127,9 @@ export function TaskInfo({
|
||||
taskObject,
|
||||
currentLoadingText,
|
||||
roles,
|
||||
isPauseWorkFlow
|
||||
isPauseWorkFlow,
|
||||
showGotoCutButton,
|
||||
onGotoCut
|
||||
}: TaskInfoProps) {
|
||||
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
|
||||
const [currentStage, setCurrentStage] = useState(0);
|
||||
@ -243,11 +249,10 @@ export function TaskInfo({
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
className="flex items-center gap-2 justify-center cursor-pointer"
|
||||
className="flex items-center gap-2 justify-center"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
onClick={() => setIsScriptModalOpen(true)}
|
||||
>
|
||||
<motion.div
|
||||
className="w-1.5 h-1.5 rounded-full"
|
||||
@ -303,7 +308,7 @@ export function TaskInfo({
|
||||
|
||||
{/* 主文字 - 颜色填充动画 */}
|
||||
<motion.div
|
||||
className="relative z-10"
|
||||
className="relative z-10 cursor-pointer"
|
||||
animate={!isPauseWorkFlow ? {
|
||||
scale: [1, 1.02, 1],
|
||||
transition: {
|
||||
@ -312,6 +317,7 @@ export function TaskInfo({
|
||||
ease: "easeInOut"
|
||||
}
|
||||
}: {}}
|
||||
onClick={() => setIsScriptModalOpen(true)}
|
||||
>
|
||||
<motion.span
|
||||
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
|
||||
@ -390,6 +396,13 @@ export function TaskInfo({
|
||||
}
|
||||
} : {}}
|
||||
/>
|
||||
|
||||
{/* 跳转剪辑按钮 */}
|
||||
{showGotoCutButton && (
|
||||
<Tooltip placement="top" title='Go to Smart Cut'>
|
||||
<GlassIconButton icon={Scissors} size='sm' onClick={onGotoCut} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
|
||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan } from '@/api/video_flow';
|
||||
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||
@ -15,6 +15,7 @@ export function useWorkflowData() {
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const episodeId = searchParams.get('episodeId') || '';
|
||||
const from = searchParams.get('from') || '';
|
||||
const token = localStorage.getItem('token') || '';
|
||||
|
||||
let tempTaskObject = useRef<TaskObject>({
|
||||
@ -49,6 +50,7 @@ export function useWorkflowData() {
|
||||
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
|
||||
const [needStreamData, setNeedStreamData] = useState(false);
|
||||
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
||||
const [canGoToCut, setCanGoToCut] = useState(false);
|
||||
const [state, setState] = useState({
|
||||
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
|
||||
originalText: '',
|
||||
@ -110,11 +112,16 @@ export function useWorkflowData() {
|
||||
}
|
||||
}, [taskObject.currentStage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (token && currentLoadingText.includes('Post-production')) {
|
||||
const generateEditPlan = useCallback(async () => {
|
||||
await getGenerateEditPlan({ project_id: episodeId });
|
||||
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}`, '_self');
|
||||
}
|
||||
}, [currentLoadingText, token, episodeId]);
|
||||
}, [episodeId]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!from && canGoToCut) {
|
||||
// generateEditPlan();
|
||||
// }
|
||||
// }, [canGoToCut]);
|
||||
|
||||
|
||||
useUpdateEffect(() => {
|
||||
@ -191,6 +198,12 @@ 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 !== 'IN_PROCESS').length;
|
||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||
if (analyze_video_completed_count === analyze_video_total_count) {
|
||||
setCanGoToCut(true);
|
||||
}
|
||||
|
||||
for (const task of all_task_data) {
|
||||
// 如果有已完成的数据,同步到状态
|
||||
@ -547,6 +560,9 @@ export function useWorkflowData() {
|
||||
setAnyAttribute,
|
||||
applyScript,
|
||||
fallbackToStep,
|
||||
originalText: state.originalText
|
||||
originalText: state.originalText,
|
||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||
showGotoCutButton: canGoToCut ? true : false,
|
||||
generateEditPlan
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user