工作流加入接口

This commit is contained in:
北枳 2025-07-04 15:46:31 +08:00
parent 9dc168f22e
commit 04de8dd6d1
4 changed files with 345 additions and 196 deletions

View File

@ -10,6 +10,8 @@ import { MediaViewer } from "./work-flow/media-viewer";
import { ThumbnailGrid } from "./work-flow/thumbnail-grid";
import { useWorkflowData } from "./work-flow/use-workflow-data";
import { usePlaybackControls } from "./work-flow/use-playback-controls";
import { AlertCircle, RefreshCw } from "lucide-react";
import { motion } from "framer-motion";
export default function WorkFlow() {
const containerRef = useRef<HTMLDivElement>(null);
@ -32,7 +34,9 @@ export default function WorkFlow() {
roles,
music,
final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
} = useWorkflowData();
const {
@ -159,6 +163,7 @@ export default function WorkFlow() {
isLoading={isLoading}
taskObject={taskObject}
currentLoadingText={currentLoadingText}
dataLoadError={dataLoadError}
/>
</ErrorBoundary>
</div>
@ -169,7 +174,38 @@ export default function WorkFlow() {
style={currentStep !== '6' ? { flex: 3 } : {}}
ref={containerRef}
>
{isLoading ? (
{dataLoadError ? (
<motion.div
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<motion.div
className="flex items-center gap-3 mb-4"
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<AlertCircle className="w-8 h-8 text-red-500" />
<h3 className="text-lg font-medium text-red-800"></h3>
</motion.div>
<p className="text-red-600 text-center mb-6 max-w-md px-4">
{dataLoadError}
</p>
<motion.button
className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
onClick={() => retryLoadData?.()}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<RefreshCw className="w-4 h-4" />
</motion.button>
</motion.div>
) : isLoading ? (
<Skeleton className="w-full aspect-video rounded-lg" />
) : (
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }}>

View File

@ -18,39 +18,138 @@ interface TaskInfoProps {
isLoading: boolean;
taskObject: any;
currentLoadingText: string;
dataLoadError?: string | null;
}
// 根据加载文本返回对应的图标
const getStageIcon = (loadingText: string) => {
const text = loadingText.toLowerCase();
if (text.includes('sketch')) {
if (text.includes('sketch') || text.includes('草图')) {
return Image;
} else if (text.includes('video')) {
} else if (text.includes('video') || text.includes('视频')) {
return Video;
} else if (text.includes('character')) {
} else if (text.includes('character') || text.includes('角色')) {
return User;
} else if (text.includes('audio')) {
} else if (text.includes('audio') || text.includes('音频')) {
return Music;
} else if (text.includes('post')) {
} else if (text.includes('post') || text.includes('后期')) {
return Scissors;
} else if (text.includes('final')) {
} else if (text.includes('final') || text.includes('最终')) {
return Tv;
} else if (text.includes('complete')) {
} else if (text.includes('complete') || text.includes('完成')) {
return CheckCircle;
} else {
return Loader2;
}
};
export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfoProps) {
export function TaskInfo({ isLoading, taskObject, currentLoadingText, dataLoadError }: TaskInfoProps) {
const StageIcon = getStageIcon(currentLoadingText);
if (isLoading) {
return (
<>
<Skeleton className="h-8 w-64 mb-2" />
<Skeleton className="h-4 w-96" />
<motion.div
className="title-JtMejk text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
{taskObject?.projectName ?
`${taskObject.projectName}${taskObject.taskName}` :
'正在加载项目信息...'
}
</motion.div>
{/* 加载状态显示 */}
<motion.div
className="flex items-center gap-2 justify-center mt-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2
}}
/>
{/* 阶段图标 */}
<motion.div
className="flex items-center gap-2"
key={currentLoadingText}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="text-blue-500"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1]
}}
transition={{
rotate: { duration: 2, repeat: Infinity, ease: "linear" },
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}}
>
<StageIcon className="w-5 h-5" />
</motion.div>
<motion.span
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "300% 300%",
}}
>
{currentLoadingText}
</motion.span>
</motion.div>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}}
/>
</motion.div>
{/* 错误提示 */}
{dataLoadError && (
<motion.div
className="mt-3 text-orange-600 text-sm text-center flex items-center justify-center gap-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<span className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></span>
{dataLoadError}
</motion.div>
)}
</>
);
}

View File

@ -1,10 +1,10 @@
'use client';
import { useState, useEffect, useRef, useCallback } from 'react';
import { getRandomMockData, STEP_MESSAGES, MOCK_DELAY_TIME } from '@/components/work-flow/constants';
import { getRandomMockData, STEP_MESSAGES, MOCK_DELAY_TIME, MOCK_DATA } from '@/components/work-flow/constants';
// 当前选择的mock数据
let selectedMockData = getRandomMockData();
let selectedMockData: any = null;
export function useWorkflowData() {
const [taskObject, setTaskObject] = useState<any>(null);
@ -17,12 +17,71 @@ export function useWorkflowData() {
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
const [isGeneratingSketch, setIsGeneratingSketch] = useState(false);
const [isGeneratingVideo, setIsGeneratingVideo] = useState(false);
const [currentLoadingText, setCurrentLoadingText] = useState('Loading task information...');
const [currentLoadingText, setCurrentLoadingText] = useState('正在加载项目数据...');
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
const [isLoadingData, setIsLoadingData] = useState(false);
// 异步加载数据 - 改进错误处理和fallback机制
const loadMockData = async () => {
try {
setIsLoadingData(true);
setDataLoadError(null);
setCurrentLoadingText('正在从服务器获取项目数据...');
// 尝试从接口获取数据
selectedMockData = await getRandomMockData();
console.log('成功从接口获取数据:', selectedMockData);
setCurrentLoadingText('项目数据加载完成');
} catch (error) {
console.warn('接口获取数据失败使用本地fallback数据:', error);
try {
// 使用本地fallback数据
setCurrentLoadingText('正在加载本地数据...');
const randomIndex = Math.floor(Math.random() * MOCK_DATA.length);
selectedMockData = MOCK_DATA[randomIndex];
console.log('使用本地fallback数据:', selectedMockData);
setCurrentLoadingText('本地数据加载完成');
} catch (fallbackError) {
console.error('本地数据加载也失败:', fallbackError);
// 最后的fallback - 直接使用第一组数据
selectedMockData = MOCK_DATA[0];
setDataLoadError('服务器连接失败,已切换到演示模式');
setCurrentLoadingText('演示数据加载完成');
}
} finally {
setIsLoadingData(false);
}
};
// 重试加载数据
const retryLoadData = async () => {
console.log('用户点击重试,重新加载数据...');
selectedMockData = null; // 重置数据
setDataLoadError(null);
setIsLoading(true);
setCurrentStep('0');
// 重新初始化整个流程
await initializeWorkflow();
};
// 模拟接口请求 获取任务详情
const getTaskDetail = async (taskId: string) => {
// 每次进入页面时重新随机选择数据
selectedMockData = getRandomMockData();
// 确保已经加载了数据
if (!selectedMockData) {
console.warn('selectedMockData为空重新加载数据');
await loadMockData();
}
// 确保数据结构正确
if (!selectedMockData || !selectedMockData.detail) {
throw new Error('数据结构不正确');
}
const data = {
projectId: selectedMockData.detail.projectId,
@ -146,13 +205,13 @@ export function useWorkflowData() {
// 更新加载文字
useEffect(() => {
if (isLoading) {
setCurrentLoadingText(STEP_MESSAGES.loading);
// 在初始加载阶段保持当前loading文字不变
return;
}
const totalSketches = selectedMockData.sketch.length;
const totalVideos = selectedMockData.video.length;
const totalCharacters = selectedMockData.roles.length;
const totalSketches = selectedMockData?.sketch?.length || 0;
const totalVideos = selectedMockData?.video?.length || 0;
const totalCharacters = selectedMockData?.roles?.length || 0;
if (currentStep === '1') {
if (isGeneratingSketch) {
@ -181,11 +240,23 @@ export function useWorkflowData() {
}
}, [isLoading, currentStep, isGeneratingSketch, sketchCount, isGeneratingVideo, taskVideos.length, taskSketch.length, taskRoles.length]);
// 初始化数据
useEffect(() => {
const taskId = localStorage.getItem("taskId") || "taskId-123";
getTaskDetail(taskId).then(async (data) => {
// 工作流初始化函数
const initializeWorkflow = async () => {
try {
setIsLoading(true);
setCurrentLoadingText('正在初始化工作流...');
const taskId = localStorage.getItem("taskId") || "taskId-123";
// 首先加载数据
await loadMockData();
// 然后获取任务详情
setCurrentLoadingText('正在加载任务详情...');
const data = await getTaskDetail(taskId);
setTaskObject(data);
// 数据加载完成,进入工作流
setIsLoading(false);
setCurrentStep('1');
@ -248,7 +319,17 @@ export function useWorkflowData() {
taskStatus: '6'
}));
setCurrentStep('6');
});
} catch (error) {
console.error('工作流初始化失败:', error);
setDataLoadError('工作流初始化失败,请刷新页面重试');
setIsLoading(false);
}
};
// 初始化数据
useEffect(() => {
initializeWorkflow();
}, []);
return {
@ -257,17 +338,19 @@ export function useWorkflowData() {
taskSketch,
taskVideos,
sketchCount,
isLoading,
isLoading: isLoading || isLoadingData, // 合并loading状态
currentStep,
currentSketchIndex,
isGeneratingSketch,
isGeneratingVideo,
currentLoadingText,
totalSketchCount: selectedMockData.sketch.length,
roles: selectedMockData.roles,
music: selectedMockData.music,
final: selectedMockData.final,
totalSketchCount: selectedMockData?.sketch?.length || 0,
roles: selectedMockData?.roles || [],
music: selectedMockData?.music || {},
final: selectedMockData?.final || {},
dataLoadError,
// 操作方法
setCurrentSketchIndex,
retryLoadData,
};
}

View File

@ -1,4 +1,4 @@
// 5组mock数据
// 5组mock数据 - 保留作为fallback数据
export const MOCK_DATA = [
{
id: 1,
@ -22,173 +22,17 @@ export const MOCK_DATA = [
url: 'https://cdn.qikongjian.com/1751484962956_7e2kv2.png',
script: 'A big tree has exposed its roots and is lying horizontally on the ground',
bg_rgb: ['RGB(12, 13, 13)', 'RGB(42, 46, 47)', 'RGB(101, 107, 109)']
},{
url: 'https://cdn.qikongjian.com/1751486620736_51i98q.png',
script: 'The leader of the factory rushed over and gave Chen Haiqing an umbrella: Director Chen, our factory\'s post disaster work has not been done yet. Why did you come in person! I asked them to report to you',
bg_rgb: ['RGB(3, 5, 8)', 'RGB(42, 46, 56)', 'RGB(133, 132, 142)']
},{
url: 'https://cdn.qikongjian.com/1751487101428_1inxi2.png',
script: 'Chen Haiqing interrupted: Don\'t waste time, disaster relief is urgent! Chen Haiqing took a few steps and continued to bend down to help move the stones.',
bg_rgb: ['RGB(42, 27, 10)', 'RGB(135, 104, 72)', 'RGB(218, 195, 168)']
},{
url: 'https://cdn.qikongjian.com/1751487200821_zp0sr8.png',
script: 'The factory leader immediately held an umbrella, feeling embarrassed',
bg_rgb: ['RGB(22, 19, 14)', 'RGB(94, 80, 62)', 'RGB(178, 165, 150)']
},{
url: 'https://cdn.qikongjian.com/1751487951909_yzcgk1.png',
script: "At this moment, a woman's shout came from inside the office building.",
bg_rgb: ['RGB(11, 12, 13)', 'RGB(61, 58, 48)', 'RGB(162, 161, 142)']
},{
url: 'https://cdn.qikongjian.com/1751488430395_erejxa.png',
script: 'Behind him, the leader of the branch factory whispered to the worker group leader: The leader of the main factory is coming. Just show him these messy things!',
bg_rgb: ['RGB(8, 13, 21)', 'RGB(53, 60, 69)', 'RGB(155, 148, 142)']
},{
url: 'https://cdn.qikongjian.com/1751488807298_nd46xl.png ',
script: 'clinic. Night. within\nOn the stool made by Director Wang, gently covering her head: Cai Xiaoyan. What\'s wrong with you! Didn\'t you see Dr. Li treating me?',
bg_rgb: ['RGB(14, 34, 28)', 'RGB(59, 112, 100)', 'RGB(189, 216, 214)']
},{
url: 'https://cdn.qikongjian.com/1751490196476_rxwwi3.jpg',
script: 'A medium shot, in a gritty 1980s realistic style, establishes the cramped infirmary, its walls a faded institutional mint green. The paunchy Director Wang [CH-06], wearing a tight, pale custard-colored shirt [COSTUME-006], is sitting on a stool in the middle of the room. The camera then smoothly pushes in to focus on him as he holds his head melodramatically. Doctor Li [CH-03], in his white doctor\'s coat [COSTUME-005], nervously dabs at a tiny cut on Wang\'s head. From his position, Director Wang [CH-06] looks towards the doorway and speaks with a whiny, indignant tone to someone off-screen.',
bg_rgb: ['RGB(83, 98, 85)', 'RGB(24, 33, 26)', 'RGB(147, 149, 143)']
},{
url: 'https://cdn.qikongjian.com/1751490347325_n9vvzm.png',
script: 'A tight close-up on the intense face of Cai Xiaoyan [CH-02], who wears a practical floral blouse [COSTUME-004]. She completely ignores Director Wang [CH-06] and locks her fiery eyes directly on Doctor Li [CH-03]. She grabs the doctor\'s arm, her grip firm and desperate. The background is a soft blur due to a very shallow depth of field, and the camera performs a slight push-in to heighten her intensity as she speaks, her voice urgent and breathless.',
bg_rgb: ['RGB(178, 185, 183)', 'RGB(130, 98, 82)', 'RGB(23, 22, 19)']
},{
url: 'https://cdn.qikongjian.com/1751490513785_f5yz9l.png',
script: 'An over-the-shoulder shot from behind Cai Xiaoyan [CH-02]. We see Doctor Li [CH-03] turn his head helplessly toward Director Wang [CH-06], who is sitting on the stool, seeking permission or a way out. He speaks weakly, abdicating responsibility.',
bg_rgb: ['RGB(161, 167, 161)', 'RGB(12, 18, 20)', 'RGB(91, 105, 106)']
},{
url: 'https://cdn.qikongjian.com/1751490589772_vfp3ea.png',
script: ' The shot starts on Director Wang [CH-06], who visibly shrinks, his head bowed in submission. A smooth rack focus shifts to a clean two-shot of Chen Haiqing [CH-01] and the Branch Factory Leader [CH-04]. Chen Haiqing [CH-01] turns away from the defeated Wang, his point made, and addresses the Leader. His tone is now calm and inquisitive, as if the confrontation meant nothing.',
bg_rgb: ['RGB(160, 170, 172)', 'RGB(18, 61, 100)', 'RGB(115, 109, 103)']
},{
url: 'https://cdn.qikongjian.com/1751490682587_ezu790.png',
script: ' The scene opens with a static camera positioned directly behind Chen Haiqing as he walks slowly down a dimly lit hallway. The focus is entirely on his back, emphasizing his solitary journey. Suddenly, Chen Haiqing comes to an abrupt halt. His sudden stillness becomes the focal point, conveying without words that he has just received significant news. This moment of absolute immobility speaks volumes, capturing the gravity of the situation.',
bg_rgb: ['RGB(39, 56, 59)', 'RGB(15, 21, 23)', 'RGB(104, 125, 130)']
},{
url: 'https://cdn.qikongjian.com/1751490741368_k5xhk3.png',
script: 'Cutting to a side angle, the camera now reveals the Branch Factory Leader standing nearby. He cautiously peers at Chen Haiqing\'s motionless figure, clearly trying to gauge the reaction. Misinterpreting the heavy silence, the Leader decides to break the tension. In a guarded, almost teasing tone, he addresses Chen Haiqing, attempting to elicit some form of response. However, his words only serve to heighten the suspense.',
bg_rgb: ['RGB(176, 171, 161)', 'RGB(20, 37, 42)', 'RGB(101, 104, 99)']
},{
url: 'https://cdn.qikongjian.com/1751490814138_oj9lcr.png',
script: 'To amplify the profound tension, the camera performs a very slow, almost imperceptible push-in towards Chen Haiqing\'s face. This subtle movement draws the viewer deeper into the moment, making the silence even more powerful. The combination of Chen Haiqing\'s unyielding stare and the camera\'s gradual advance underscores the high stakes of the interaction, leaving the audience hanging on the edge of their seats. The scene ends on this note of intense, unresolved tension.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
},{
url: 'https://cdn.qikongjian.com/1751490898729_lx041y.png',
script: 'The camera captures Chen Haiqing in a contemplative medium shot, his eyes reflecting deep concern as he processes the weight of the situation. The lighting creates dramatic shadows across his face, emphasizing the burden of leadership he carries. His expression shifts subtly from determination to worry, revealing the human side behind his authoritative facade.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
},{
url: 'https://cdn.qikongjian.com/1751490969275_xpgcb6.png',
script: 'A wide establishing shot reveals the industrial setting in its full gritty reality. Steam rises from various pipes and machinery, creating an atmosphere of urgency and chaos. Workers move like shadows in the background, their silhouettes barely visible through the haze, while the harsh fluorescent lighting casts an unforgiving glow over the entire scene.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
},{
url: 'https://cdn.qikongjian.com/1751491088171_7de9dx.png',
script: 'The camera employs a handheld technique to follow Chen Haiqing as he navigates through the crowded workspace. The slightly unstable movement adds urgency to his mission, while workers part to make way for him. His focused stride and unwavering gaze demonstrate his determination to address the crisis at hand.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
},{
url: 'https://cdn.qikongjian.com/1751491207572_s1vnp9.png',
script: 'A dramatic low-angle shot emphasizes Chen Haiqing\'s commanding presence as he stands amid the industrial chaos. The camera looks up at him, creating a sense of authority and power. Steam and smoke swirl around him like a halo, while the harsh overhead lighting creates strong contrasts that highlight his determined expression.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
},{
url: 'https://cdn.qikongjian.com/1751491276753_rgvpck.png',
script: 'The scene transitions to a quiet moment where Chen Haiqing pauses near a window, gazing out at the disaster-stricken landscape. The camera captures him in profile, creating a silhouette against the pale morning light. This contemplative beat allows the audience to see his vulnerability beneath the strong exterior.',
bg_rgb: ['RGB(19, 22, 22)', 'RGB(49, 60, 63)', 'RGB(165, 134, 113)']
}],
roles: [{ // role_count个角色
name: '陈海清',
url: 'https://cdn.qikongjian.com/1751486026891_zieiq5.png',
sound: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
soundDescription: 'A deep, controlled baritone voice with measured cadence and authoritative undertones. His speech is deliberate and precise, with slight pauses that suggest careful consideration of each word. The voice carries a subtle intensity that commands attention without being loud, reflecting his calm yet penetrating demeanor. There\'s a slight gravelly quality that hints at his working-class background, but his articulation is clear and confident.',
roleDescription: 'Brief: A portrait of a male in his late 20s or early 30s from South Korea, with a neat, side-parted hairstyle. He has an intense, focused gaze, and a calm, observant, and authoritative demeanor, small scar in his left face, eyes down.\nRole: Protagonist\nGender: Male\nPhysique & Age: Late 20s. Lean, wiry build suggesting practical strength rather than gym fitness. Stands tall with a naturally confident posture.\nKey Visual Anchors: A thin, faded white scar, about an inch long, running vertically on his left cheekbone, just below the eye. It\'s subtle and only visible in certain light. His hands are capable and calloused, evidence of a man who is not afraid of work.\nHairstyle: Short, neat, dark hair, always impeccably side-parted, even in the midst of chaos.\nDefault Demeanor: Calm and observant. His expression is typically neutral, with an intense, focused gaze that makes others feel he is seeing right through them. He moves with purpose and efficiency.',
},{
name: '蔡晓艳',
url: 'https://cdn.qikongjian.com/1751491922865_ryc7be.png',
sound: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
soundDescription: 'A passionate, mid-range female voice with fiery inflection and rapid delivery that reflects her impatient nature. Her tone shifts quickly from urgent concern to fierce determination, with a slight breathless quality that suggests she\'s always ready for action. When angry, her voice becomes sharp and cutting, but maintains clarity even in emotional moments. There\'s an underlying warmth that shows her genuine care for others, though it\'s often overshadowed by her righteous indignation.',
roleDescription: 'Brief: A medium close-up shot features a young adult South Korean woman with long, flowing dark brown hair. Her hair cascades over her shoulders and back, with some strands artfully pushed behind her right ear. Her fair complexion is illuminated by the soft lighting, highlighting her delicate facial features. She has a high forehead, well-defined eyebrows, and dark eyes that gaze directly at the viewer. Her nose is narrow and her lips are full, with a natural, light pink hue.\nRole: Female Lead\nGender: Female\nPhysique & Age: Mid-20s. Slender but resilient frame. Her movements are quick and decisive, fueled by nervous energy and fierce determination.\nKey Visual Anchors: Her long, dark brown hair is often slightly disheveled from work and stress, with loose strands framing her face. She has a habit of pushing her hair back impatiently. Her direct, unblinking dark eyes are her most powerful feature.\nHairstyle: Long, dark brown hair, naturally wavy. Mostly worn down due to practicality, but she pushes it back from her face when focused or angry.\nDefault Demeanor: Fiery, passionate, and defiant. She carries an air of righteous anger and impatience with incompetence. Her default expression is a worried frown, but it can quickly flash into a determined glare.',
},{
name: '李医生',
url: 'https://cdn.qikongjian.com/1751489036973_3ytd1e.png',
sound: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
soundDescription: 'A gentle, soft-spoken tenor voice with hesitant delivery and frequent pauses that betray his uncertainty. His speech has a scholarly quality with proper enunciation, but lacks confidence and authority. When nervous, he tends to speak more quietly and clear his throat frequently. There\'s a compassionate warmth in his tone when discussing patients, revealing his genuine care despite his institutional constraints. His voice occasionally wavers when faced with difficult decisions.',
roleDescription: 'Brief: A close-up, eye-level, chest-up portrait shot features an adult male with light skin and short, dark hair, styled neatly, with some strands combed forward over his forehead. He has dark eyebrows and is wearing thin-rimmed, round glasses. His eyes are dark, and he has a subtle smile with his lips closed. An asia south korea man.\nRole: Supporting Character\nGender: Male\nPhysique & Age: Early 30s. Thin, slightly stooped posture from poring over patients and books. Not physically imposing.\nKey Visual Anchors: His thin, wire-rimmed round glasses are his defining feature. He is constantly pushing them up the bridge of his nose, especially when nervous or thinking.\nHairstyle: Short, neat dark hair, slightly overgrown, with a fringe that falls over his forehead.\nDefault Demeanor: Hesitant and conflict-averse. He is a good man caught between duty and bureaucracy. His expression is often one of weariness and indecision.',
},{
name: '分厂领导',
url: 'https://cdn.qikongjian.com/1751486823013_5wx7fm.png',
sound: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
soundDescription: 'A nervous, mid-range male voice with a tendency to speak too quickly when under pressure. His tone is perpetually anxious and overly accommodating, with a forced cheerfulness that barely masks his insecurity. When trying to appease superiors, his voice becomes higher and more strained, often punctuated by nervous laughter. He frequently uses filler words and repeats himself, especially when explaining away problems or making excuses.',
roleDescription: 'Brief: A studio portrait shows a male subject from the chest up, positioned slightly off-center to the left, with his head tilted slightly to the right. He is in his twenties or early thirties, with fair skin and dark, short, styled hair. He has a subtle smile, and his eyes are dark. south korea, asian.\nRole: Supporting Character\nGender: Male\nPhysique & Age: Early 30s. A bit soft around the middle, clearly unused to manual labor. Fair skin contrasts with the grimy environment.\nKey Visual Anchors: Wears a wristwatch that is slightly too nice for his position, which he nervously glances at. Perspires easily under pressure, with a constant sheen of sweat on his forehead.\nHairstyle: Short, neatly styled dark hair that he tries to keep in place, but which becomes slightly disheveled in the rain and chaos.\nDefault Demeanor: Sycophantic and anxious. He is perpetually trying to please his superiors and maintain control, but is clearly out of his depth. His smile is quick but insincere.',
},{
name: '王主任',
url: 'https://cdn.qikongjian.com/1751488727632_olhrxa.png',
sound: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
soundDescription: 'A pompous, nasal baritone voice with a whiny, complaining quality that immediately grates on listeners. His speech is self-important and condescending, with exaggerated pronunciation when he wants to sound authoritative. When faced with real problems, his voice becomes petulant and accusatory, rising in pitch as his frustration mounts. He frequently interrupts others and speaks with dramatic emphasis, as if every minor inconvenience is a personal affront.',
roleDescription: 'Role: Minor Antagonist\nGender: Male\nPhysique & Age: Late 40s. Paunchy, with a soft physique that speaks to a sedentary desk job. His face is fleshy and prone to turning red when he\'s angry or exerting himself.\nKey Visual Anchors: A noticeable bald spot on the crown of his head, surrounded by thinning, carefully combed-over graying hair. He clutches his head in the infirmary not just from a supposed injury, but as a gesture of dramatic self-pity.\nHairstyle: Thinning, graying dark hair, combed over a prominent bald spot.\nDefault Demeanor: Pompous, selfish, and cowardly. He carries himself with an unearned sense of importance, speaking in a whiny, complaining tone. His default expression is one of slighted entitlement.',
soundDescription: 'A deep, controlled baritone voice with measured cadence and authoritative undertones.',
roleDescription: 'Brief: A portrait of a male in his late 20s or early 30s from South Korea.',
}],
video: [{ // sketch_count个分镜视频以及对应的音频
url: 'https://cdn.qikongjian.com/1751483686756_01p12v.mp4',
script: 'After the flood disaster in the middle and lower reaches of the Yangtze River in 1981\nFlood Documentary Camera\nAccompanied by the sound of news broadcasts.',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751486094044_ow9qrf.mp4',
script: 'The leader of the factory rushed over and gave Chen Haiqing an umbrella: Director Chen, our factory\'s post disaster work has not been done yet. Why did you come in person! I asked them to report to you',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751487266547_exqyvu.mp4',
script: 'At this moment, the leader of the factory rushed over and gave Chen Haiqing an umbrella: Director Chen, our factory\'s post disaster work has not been done yet. Why did you come in person! I asked them to report to you\nChen Haiqing interrupted: Don\'t waste time, disaster relief is urgent!\nChen Haiqing took a few steps and continued to bend down to help move the stones.\nThe factory leader immediately held an umbrella, feeling embarrassed',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751487608876_y8vu6z.mp4',
script: 'At this moment, a woman\'s shout came from inside the office building.\nOff screen voice of Cai Xiaoyan:\nDr. Li! If you don\'t come with me again, I\'ll smash your clinic!\nChen Haiqing looked up.',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751488183017_z6rrec.mp4',
script: 'Behind him, the leader of the branch factory whispered to the worker group leader: The leader of the main factory is coming. Just show him these messy things!',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751488527285_2hwsii.mp4',
script: 'clinic. Night. within\nOn the stool made by Director Wang, gently covering her head: Cai Xiaoyan. What\'s wrong with you! Didn\'t you see Dr. Li treating me?\nCai Xiaoyan looked at Dr. Li and pulled the standing Dr. Li: Dr. Li, my sister-in-law has been trapped in the workshop for several days with a big belly. Now there\'s massive bleeding!',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751489110005_hd1erq.mp4',
script: 'Cai Xiaoyan looked at Dr. Li and pulled the standing Dr. Li: Dr. Li, my sister-in-law has been trapped in the workshop for several days with a big belly. Now there\'s massive bleeding!\nDr. Li looked at her and said, \'Wait, wait, my wound hasn\'t been examined yet.\'',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751489834974_t1easr.mp4',
script: 'Cai Xiaoyan stepped forward and gave Director Wang a glare.\nCai Xiaoyan was furious: Check, check again, the wound is almost healed!',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490211998_n5n70s.mp4',
script: 'Director Wang: How do you know that my injuries won\'t kill anyone? What if they get infected? The toxin may have already entered the skull!',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490362185_lq90ih.mp4',
script: 'Cai Xiaoyan: Then you can endure it, you can\'t die!\nDirector Wang snorted lightly, but Cai Xiaoyan ignored him and just pulled the doctor away\nCai Xiaoyan: Dr. Li, come with me. He\'s dead, and I\'ll take responsibility!',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490546133_sylhql.mp4',
script: 'At this moment, Cai Xiaoyan pulled Dr. Li out of the medical room. Passing by Chen Haiqing standing at the door.\nThis bright and familiar face flashed before Chen Haiqing\'s eyes, and he was pleasantly surprised',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490641482_6zs3di.mp4',
script: 'Director Wang cursed and chased after: Cai Xiaoyan! You wait (I\'m so angry)',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490803092_aamxzm.mp4',
script: 'Chen Haiqing blocked Director Wang\'s path and looked at him coldly.\nDirector Wang: Who are you?\nThe leader of the branch factory ran over and waved his hand, indicating that Director Wang should stop talking',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751490938703_otnf61.mp4',
script: 'Chen Haiqing: Wang Rixin from the finance department of the first refrigeration factory, is that you? You don\'t need to participate in the selection of outstanding cadres at the end of the year.',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751491433385_8ewmia.mp4',
script: 'Factory leader: Her name is Cai Xiaoyan, a famous crooked woman in our factory! got divorced! There\'s also a son!\nChen Haiqing was taken aback and his footsteps stopped.',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
},{
url: 'https://cdn.qikongjian.com/1751491627383_dfi3f2.mp4',
script: 'Chen Haiqing couldn\'t help but smile. Oh, so she\'s divorced',
script: 'After the flood disaster in the middle and lower reaches of the Yangtze River in 1981',
audio: 'https://cdn.qikongjian.com/audio/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp3',
}],
music: {
@ -200,15 +44,102 @@ export const MOCK_DATA = [
isLooped: true,
},
final: {
url: 'https://cdn.huiying.video/final.mp4'
url: 'https://cdn.qikongjian.com/1751593771075_0lxy9f.mp4'
},
}
}
];
// 随机选择一组mock数据
export const getRandomMockData = () => {
const randomIndex = Math.floor(Math.random() * MOCK_DATA.length);
return MOCK_DATA[randomIndex];
// 从接口获取数据的函数
export const fetchMockDataFromAPI = async () => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
console.log('开始请求接口数据...');
const response = await fetch('https://movieflow.api.huiying.video/serversetting/roadshow-configs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
}),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`接口请求失败: HTTP ${response.status} - ${response.statusText}`);
}
const result = await response.json();
console.log('接口返回数据:', result);
// 验证返回数据格式
if (result.code !== 0) {
throw new Error(`接口错误: ${result.message || '未知错误'} (code: ${result.code})`);
}
if (!result.successful) {
throw new Error(`接口调用不成功: ${result.message || '未知原因'}`);
}
if (!result.data || !Array.isArray(result.data) || result.data.length === 0) {
throw new Error('接口返回数据格式错误或为空');
}
// 验证数据结构
let validData: any[] = [];
result.data.forEach((item: any) => {
if (item) {
validData.push(JSON.parse(item));
}
});
if (validData.length === 0) {
throw new Error('接口返回的数据格式不正确');
}
console.log('成功获取并验证接口数据:', validData);
return validData;
} catch (error: unknown) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('接口请求超时,请检查网络连接');
}
console.error('接口请求失败:', error);
throw error;
}
};
// 异步获取随机数据 - 从接口或fallback到本地数据
export const getRandomMockData = async () => {
try {
const apiData = await fetchMockDataFromAPI();
const randomIndex = Math.floor(Math.random() * apiData.length);
const selectedData = apiData[randomIndex];
console.log('从接口数据中随机选择:', selectedData);
return selectedData;
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : '未知错误';
console.warn('接口获取数据失败使用本地fallback数据:', errorMessage);
// 确保本地数据结构正确
if (!MOCK_DATA || MOCK_DATA.length === 0) {
throw new Error('本地fallback数据也不可用');
}
const randomIndex = Math.floor(Math.random() * MOCK_DATA.length);
const selectedData = MOCK_DATA[randomIndex];
console.log('使用本地fallback数据:', selectedData);
throw error; // 重新抛出错误让上层知道是fallback数据
}
};
export interface TaskObject {