forked from 77media/video-flow
更新 BASE_URL 为生产环境地址,添加生成剪辑计划接口及相关类型,修改工作流组件以支持跳转剪辑功能,优化标题和按钮文本。
This commit is contained in:
parent
b4d17d237b
commit
37df90f649
@ -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'
|
// export const BASE_URL ='http://192.168.120.5:8000'
|
||||||
//
|
//
|
||||||
|
|||||||
@ -183,6 +183,28 @@ export type ConvertScenePromptRequest =
|
|||||||
// 转换分镜头响应接口
|
// 转换分镜头响应接口
|
||||||
export type ConvertScenePromptResponse = BaseApiResponse<ScenePrompts>;
|
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 自动判断是剧本还是视频模式
|
* @param request - 请求参数,根据 project_type 自动判断是剧本还是视频模式
|
||||||
@ -269,6 +291,11 @@ export const getRunningStreamData = async (data: {
|
|||||||
return post<ApiResponse<any>>("/movie/get_status", 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 - 请求参数
|
* @param data - 请求参数
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head>
|
<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="description" content="Professional AI-powered video creation platform with advanced editing tools" />
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export function MessageRenderer({ msg }: MessageRendererProps) {
|
|||||||
src={b.url}
|
src={b.url}
|
||||||
poster={b.poster}
|
poster={b.poster}
|
||||||
className="w-full max-h-80 bg-black"
|
className="w-full max-h-80 bg-black"
|
||||||
controlsList="nodownload noremoteplayback"
|
controlsList="noremoteplayback"
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
disableRemotePlayback
|
disableRemotePlayback
|
||||||
onContextMenu={e => e.preventDefault()}
|
onContextMenu={e => e.preventDefault()}
|
||||||
|
|||||||
@ -133,8 +133,8 @@ export default function SmartChatBox({
|
|||||||
<span>Chat</span>
|
<span>Chat</span>
|
||||||
{/* System push toggle */}
|
{/* System push toggle */}
|
||||||
<Switch
|
<Switch
|
||||||
checkedChildren="System push: On"
|
checkedChildren="System: On"
|
||||||
unCheckedChildren="System push: Off"
|
unCheckedChildren="System: Off"
|
||||||
checked={systemPush}
|
checked={systemPush}
|
||||||
onChange={toggleSystemPush}
|
onChange={toggleSystemPush}
|
||||||
className="ml-2 "
|
className="ml-2 "
|
||||||
|
|||||||
@ -52,7 +52,9 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText
|
originalText,
|
||||||
|
showGotoCutButton,
|
||||||
|
generateEditPlan
|
||||||
} = useWorkflowData();
|
} = useWorkflowData();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -91,6 +93,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
currentLoadingText={currentLoadingText}
|
currentLoadingText={currentLoadingText}
|
||||||
roles={taskObject.roles.data}
|
roles={taskObject.roles.data}
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
|
showGotoCutButton={showGotoCutButton}
|
||||||
|
onGotoCut={generateEditPlan}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,12 +11,16 @@ import {
|
|||||||
Scissors
|
Scissors
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
|
||||||
interface TaskInfoProps {
|
interface TaskInfoProps {
|
||||||
taskObject: TaskObject;
|
taskObject: TaskObject;
|
||||||
currentLoadingText: string;
|
currentLoadingText: string;
|
||||||
roles: any[];
|
roles: any[];
|
||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
|
showGotoCutButton: boolean;
|
||||||
|
onGotoCut?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stageIconMap = {
|
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 }) => {
|
const StageIcons = ({ currentStage, isExpanded, isPauseWorkFlow }: { currentStage: number, isExpanded: boolean, isPauseWorkFlow: boolean }) => {
|
||||||
@ -123,7 +127,9 @@ export function TaskInfo({
|
|||||||
taskObject,
|
taskObject,
|
||||||
currentLoadingText,
|
currentLoadingText,
|
||||||
roles,
|
roles,
|
||||||
isPauseWorkFlow
|
isPauseWorkFlow,
|
||||||
|
showGotoCutButton,
|
||||||
|
onGotoCut
|
||||||
}: TaskInfoProps) {
|
}: TaskInfoProps) {
|
||||||
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
|
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
|
||||||
const [currentStage, setCurrentStage] = useState(0);
|
const [currentStage, setCurrentStage] = useState(0);
|
||||||
@ -243,11 +249,10 @@ export function TaskInfo({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<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 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
onClick={() => setIsScriptModalOpen(true)}
|
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-1.5 h-1.5 rounded-full"
|
className="w-1.5 h-1.5 rounded-full"
|
||||||
@ -303,7 +308,7 @@ export function TaskInfo({
|
|||||||
|
|
||||||
{/* 主文字 - 颜色填充动画 */}
|
{/* 主文字 - 颜色填充动画 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative z-10"
|
className="relative z-10 cursor-pointer"
|
||||||
animate={!isPauseWorkFlow ? {
|
animate={!isPauseWorkFlow ? {
|
||||||
scale: [1, 1.02, 1],
|
scale: [1, 1.02, 1],
|
||||||
transition: {
|
transition: {
|
||||||
@ -312,6 +317,7 @@ export function TaskInfo({
|
|||||||
ease: "easeInOut"
|
ease: "easeInOut"
|
||||||
}
|
}
|
||||||
}: {}}
|
}: {}}
|
||||||
|
onClick={() => setIsScriptModalOpen(true)}
|
||||||
>
|
>
|
||||||
<motion.span
|
<motion.span
|
||||||
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
|
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>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
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 { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||||
|
import { all } from 'axios';
|
||||||
|
|
||||||
export function useWorkflowData() {
|
export function useWorkflowData() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -15,6 +16,7 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId') || '';
|
const episodeId = searchParams.get('episodeId') || '';
|
||||||
|
const from = searchParams.get('from') || '';
|
||||||
const token = localStorage.getItem('token') || '';
|
const token = localStorage.getItem('token') || '';
|
||||||
|
|
||||||
let tempTaskObject = useRef<TaskObject>({
|
let tempTaskObject = useRef<TaskObject>({
|
||||||
@ -49,6 +51,7 @@ export function useWorkflowData() {
|
|||||||
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
|
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
|
||||||
const [needStreamData, setNeedStreamData] = useState(false);
|
const [needStreamData, setNeedStreamData] = useState(false);
|
||||||
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
||||||
|
const [canGoToCut, setCanGoToCut] = useState(false);
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
|
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
|
||||||
originalText: '',
|
originalText: '',
|
||||||
@ -110,11 +113,16 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}, [taskObject.currentStage]);
|
}, [taskObject.currentStage]);
|
||||||
|
|
||||||
|
const generateEditPlan = useCallback(async () => {
|
||||||
|
await getGenerateEditPlan({ project_id: episodeId });
|
||||||
|
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}`, '_self');
|
||||||
|
}, [episodeId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token && currentLoadingText.includes('Post-production')) {
|
if (!from && canGoToCut) {
|
||||||
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}`, '_self');
|
generateEditPlan();
|
||||||
}
|
}
|
||||||
}, [currentLoadingText, token, episodeId]);
|
}, [canGoToCut]);
|
||||||
|
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
@ -191,6 +199,12 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
// 收集所有需要更新的状态
|
// 收集所有需要更新的状态
|
||||||
let stateUpdates = JSON.stringify(taskCurrent);
|
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) {
|
for (const task of all_task_data) {
|
||||||
// 如果有已完成的数据,同步到状态
|
// 如果有已完成的数据,同步到状态
|
||||||
@ -547,6 +561,8 @@ export function useWorkflowData() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText: state.originalText
|
originalText: state.originalText,
|
||||||
|
showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||||
|
generateEditPlan
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user