Video_AdGenesis_App / frontend /src /context /GenerationContext.tsx
sushilideaclan01's picture
Add video cancellation feature and update API integration
66e744c
import React, { createContext, useContext, useReducer, ReactNode } from 'react';
import type {
GenerationState,
GenerationStep,
VideoProvider,
VeoSegment,
GeneratedVideo
} from '@/types';
// Initial state
const initialState: GenerationState = {
step: 'idle',
provider: null,
segments: [],
currentSegmentIndex: 0,
generatedVideos: [],
progress: {
current: 0,
total: 0,
message: '',
},
error: null,
taskId: null,
retryState: null,
activeTaskIds: [] as string[],
isCancelling: false,
};
// Action types
type GenerationAction =
| { type: 'SET_PROVIDER'; payload: VideoProvider }
| { type: 'SET_STEP'; payload: GenerationStep }
| { type: 'SET_SEGMENTS'; payload: VeoSegment[] }
| { type: 'SET_CURRENT_SEGMENT'; payload: number }
| { type: 'ADD_GENERATED_VIDEO'; payload: GeneratedVideo }
| { type: 'SET_PROGRESS'; payload: { current?: number; total?: number; message?: string } }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'SET_TASK_ID'; payload: string | null }
| { type: 'SET_RETRY_STATE'; payload: { failedSegmentIndex: number; error: string } | null }
| { type: 'ADD_TASK_ID'; payload: string }
| { type: 'REMOVE_TASK_ID'; payload: string }
| { type: 'SET_CANCELLING'; payload: boolean }
| { type: 'RESET' };
// Reducer
function generationReducer(state: GenerationState, action: GenerationAction): GenerationState {
switch (action.type) {
case 'SET_PROVIDER':
return { ...state, provider: action.payload };
case 'SET_STEP':
return { ...state, step: action.payload };
case 'SET_SEGMENTS':
return { ...state, segments: action.payload };
case 'SET_CURRENT_SEGMENT':
return { ...state, currentSegmentIndex: action.payload };
case 'ADD_GENERATED_VIDEO':
return {
...state,
generatedVideos: [...state.generatedVideos, action.payload]
};
case 'SET_PROGRESS':
return {
...state,
progress: { ...state.progress, ...action.payload }
};
case 'SET_ERROR':
return { ...state, error: action.payload, step: action.payload ? 'error' : state.step };
case 'SET_TASK_ID':
return { ...state, taskId: action.payload };
case 'SET_RETRY_STATE':
return { ...state, retryState: action.payload };
case 'ADD_TASK_ID':
return { ...state, activeTaskIds: [...state.activeTaskIds, action.payload] };
case 'REMOVE_TASK_ID':
return { ...state, activeTaskIds: state.activeTaskIds.filter(id => id !== action.payload) };
case 'SET_CANCELLING':
return { ...state, isCancelling: action.payload };
case 'RESET':
return { ...initialState, provider: state.provider };
default:
return state;
}
}
// Context
interface GenerationContextValue {
state: GenerationState;
dispatch: React.Dispatch<GenerationAction>;
// Helper actions
selectProvider: (provider: VideoProvider) => void;
setStep: (step: GenerationStep) => void;
startGeneration: (segments: VeoSegment[]) => void;
advanceSegment: () => void;
addVideo: (video: GeneratedVideo) => void;
updateProgress: (message: string, current?: number, total?: number) => void;
setError: (error: string | null) => void;
setRetryState: (state: { failedSegmentIndex: number; error: string } | null) => void;
updateSegments: (segments: VeoSegment[]) => void;
addTaskId: (taskId: string) => void;
removeTaskId: (taskId: string) => void;
cancelGeneration: () => Promise<void>;
reset: () => void;
}
const GenerationContext = createContext<GenerationContextValue | null>(null);
// Provider component
export function GenerationProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(generationReducer, initialState);
const value: GenerationContextValue = {
state,
dispatch,
selectProvider: (provider) => {
dispatch({ type: 'SET_PROVIDER', payload: provider });
dispatch({ type: 'SET_STEP', payload: 'configuring' });
},
setStep: (step) => {
dispatch({ type: 'SET_STEP', payload: step });
},
startGeneration: (segments) => {
dispatch({ type: 'SET_SEGMENTS', payload: segments });
dispatch({ type: 'SET_CURRENT_SEGMENT', payload: 0 });
dispatch({ type: 'SET_PROGRESS', payload: { current: 0, total: segments.length, message: 'Starting generation...' } });
dispatch({ type: 'SET_STEP', payload: 'generating_video' });
},
advanceSegment: () => {
const nextIndex = state.currentSegmentIndex + 1;
dispatch({ type: 'SET_CURRENT_SEGMENT', payload: nextIndex });
dispatch({ type: 'SET_PROGRESS', payload: { current: nextIndex } });
},
addVideo: (video) => {
dispatch({ type: 'ADD_GENERATED_VIDEO', payload: video });
},
updateProgress: (message, current, total) => {
dispatch({
type: 'SET_PROGRESS',
payload: {
message,
...(current !== undefined && { current }),
...(total !== undefined && { total })
}
});
},
setError: (error) => {
dispatch({ type: 'SET_ERROR', payload: error });
},
setRetryState: (retryState) => {
dispatch({ type: 'SET_RETRY_STATE', payload: retryState });
},
updateSegments: (segments) => {
dispatch({ type: 'SET_SEGMENTS', payload: segments });
},
addTaskId: (taskId) => {
dispatch({ type: 'ADD_TASK_ID', payload: taskId });
},
removeTaskId: (taskId) => {
dispatch({ type: 'REMOVE_TASK_ID', payload: taskId });
},
cancelGeneration: async () => {
dispatch({ type: 'SET_CANCELLING', payload: true });
try {
const { klingCancel } = await import('@/utils/api');
// Cancel all active tasks
const currentTaskIds = [...state.activeTaskIds];
const cancelPromises = currentTaskIds.map(taskId =>
klingCancel(taskId).catch(err => {
console.warn(`Failed to cancel task ${taskId}:`, err);
})
);
await Promise.all(cancelPromises);
// Clear all task IDs
currentTaskIds.forEach(id => {
dispatch({ type: 'REMOVE_TASK_ID', payload: id });
});
dispatch({ type: 'SET_TASK_ID', payload: null });
dispatch({ type: 'SET_ERROR', payload: 'Generation cancelled by user' });
dispatch({ type: 'SET_STEP', payload: 'error' });
} catch (error) {
console.error('Error cancelling generation:', error);
dispatch({ type: 'SET_ERROR', payload: 'Failed to cancel generation' });
} finally {
dispatch({ type: 'SET_CANCELLING', payload: false });
}
},
reset: () => {
dispatch({ type: 'RESET' });
},
};
return (
<GenerationContext.Provider value={value}>
{children}
</GenerationContext.Provider>
);
}
// Hook
export function useGeneration() {
const context = useContext(GenerationContext);
if (!context) {
throw new Error('useGeneration must be used within GenerationProvider');
}
return context;
}