Files
ALwrity/frontend/src/services/storyWriterApi.ts
2025-11-17 17:38:23 +05:30

546 lines
14 KiB
TypeScript

import { aiApiClient, pollingApiClient } from "../api/client";
/**
* Story Writer API Service
*
* Provides TypeScript-typed API calls for story generation endpoints.
*/
export interface StoryGenerationRequest {
persona: string;
story_setting: string;
character_input: string;
plot_elements: string;
writing_style: string;
story_tone: string;
narrative_pov: string;
audience_age_group: string;
content_rating: string;
ending_preference: string;
story_length?: string;
enable_explainer?: boolean;
enable_illustration?: boolean;
enable_narration?: boolean;
enable_video_narration?: boolean;
// Image generation settings
image_provider?: string;
image_width?: number;
image_height?: number;
image_model?: string;
// Video generation settings
video_fps?: number;
video_transition_duration?: number;
// Audio generation settings
audio_provider?: string;
audio_lang?: string;
audio_slow?: boolean;
audio_rate?: number;
}
export interface StorySetupGenerationRequest {
story_idea: string;
}
export interface StorySetupOption {
persona: string;
story_setting: string;
character_input: string;
plot_elements: string;
writing_style: string;
story_tone: string;
narrative_pov: string;
audience_age_group: string;
content_rating: string;
ending_preference: string;
story_length?: string;
premise: string;
reasoning: string;
// Image generation settings
image_provider?: string;
image_width?: number;
image_height?: number;
image_model?: string;
// Video generation settings
video_fps?: number;
video_transition_duration?: number;
// Audio generation settings
audio_provider?: string;
audio_lang?: string;
audio_slow?: boolean;
audio_rate?: number;
}
export interface StorySetupGenerationResponse {
options: StorySetupOption[];
success: boolean;
}
export interface StoryPremiseResponse {
premise: string;
success: boolean;
task_id?: string;
}
export interface StoryScene {
scene_number: number;
title: string;
description: string;
image_prompt: string;
audio_narration: string;
character_descriptions?: string[];
key_events?: string[];
}
export interface StoryOutlineResponse {
outline: string | StoryScene[];
success: boolean;
task_id?: string;
is_structured?: boolean;
}
export interface StoryContentResponse {
story: string;
premise?: string;
outline?: string;
is_complete: boolean;
iterations: number;
success: boolean;
task_id?: string;
}
export interface StoryFullGenerationResponse {
premise: string;
outline: string;
story: string;
is_complete: boolean;
iterations: number;
success: boolean;
task_id?: string;
}
export interface StoryStartRequest extends StoryGenerationRequest {
premise: string;
outline: string | StoryScene[];
}
export interface StoryContinueRequest extends StoryGenerationRequest {
premise: string;
outline: string | StoryScene[];
story_text: string;
}
export interface StoryContinueResponse {
continuation: string;
is_complete: boolean;
success: boolean;
}
export interface TaskStatus {
task_id: string;
status: "pending" | "processing" | "completed" | "failed";
progress?: number;
message?: string;
result?: any;
error?: string;
created_at?: string;
updated_at?: string;
}
export interface CacheStats {
total_entries: number;
cache_keys: string[];
}
export interface StoryImageGenerationRequest {
scenes: StoryScene[];
provider?: string;
width?: number;
height?: number;
model?: string;
}
export interface StoryImageResult {
scene_number: number;
scene_title: string;
image_filename: string;
image_url: string;
width: number;
height: number;
provider: string;
model?: string;
seed?: number;
error?: string;
}
export interface StoryImageGenerationResponse {
images: StoryImageResult[];
success: boolean;
task_id?: string;
}
export interface StoryAudioGenerationRequest {
scenes: StoryScene[];
provider?: string;
lang?: string;
slow?: boolean;
rate?: number;
}
export interface StoryAudioResult {
scene_number: number;
scene_title: string;
audio_filename: string;
audio_url: string;
provider: string;
file_size: number;
error?: string;
}
export interface StoryAudioGenerationResponse {
audio_files: StoryAudioResult[];
success: boolean;
task_id?: string;
}
export interface StoryVideoGenerationRequest {
scenes: StoryScene[];
image_urls: string[];
audio_urls: string[];
story_title?: string;
fps?: number;
transition_duration?: number;
}
export interface StoryVideoResult {
video_filename: string;
video_url: string;
duration: number;
fps: number;
file_size: number;
num_scenes: number;
error?: string;
}
export interface StoryVideoGenerationResponse {
video: StoryVideoResult;
success: boolean;
task_id?: string;
}
class StoryWriterApi {
/**
* Generate 3 story setup options from a user's story idea
*/
async generateStorySetup(
request: StorySetupGenerationRequest
): Promise<StorySetupGenerationResponse> {
const response = await aiApiClient.post<StorySetupGenerationResponse>(
"/api/story/generate-setup",
request
);
return response.data;
}
/**
* Generate a story premise
*/
async generatePremise(request: StoryGenerationRequest): Promise<StoryPremiseResponse> {
const response = await aiApiClient.post<StoryPremiseResponse>(
"/api/story/generate-premise",
request
);
return response.data;
}
/**
* Generate a story outline from a premise
*/
async generateOutline(
premise: string,
request: StoryGenerationRequest
): Promise<StoryOutlineResponse> {
// Create StoryStartRequest with premise included
const outlineRequest: StoryStartRequest = {
...request,
premise: premise,
outline: [], // Empty outline for outline generation
};
const response = await aiApiClient.post<StoryOutlineResponse>(
`/api/story/generate-outline`,
outlineRequest
);
return response.data;
}
/**
* Generate the starting section of a story
*/
async generateStoryStart(
premise: string,
outline: string | StoryScene[],
request: StoryGenerationRequest
): Promise<StoryContentResponse> {
// Create request body with premise and outline
const requestBody = {
...request,
premise,
outline,
};
const response = await aiApiClient.post<StoryContentResponse>(
`/api/story/generate-start`,
requestBody
);
return response.data;
}
/**
* Continue writing a story
*/
async continueStory(request: StoryContinueRequest): Promise<StoryContinueResponse> {
const response = await aiApiClient.post<StoryContinueResponse>(
"/api/story/continue",
request
);
return response.data;
}
/**
* Generate a complete story asynchronously
* Returns a task_id for polling
*/
async generateFullStory(
request: StoryGenerationRequest,
maxIterations: number = 10
): Promise<{ task_id: string; status: string; message: string }> {
const response = await aiApiClient.post<{ task_id: string; status: string; message: string }>(
"/api/story/generate-full",
request,
{
params: { max_iterations: maxIterations },
}
);
return response.data;
}
/**
* Get the status of a story generation task
*/
async getTaskStatus(taskId: string): Promise<TaskStatus> {
const response = await pollingApiClient.get<TaskStatus>(
`/api/story/task/${taskId}/status`
);
return response.data;
}
/**
* Get the result of a completed story generation task
*/
async getTaskResult(taskId: string): Promise<StoryFullGenerationResponse> {
const response = await aiApiClient.get<StoryFullGenerationResponse>(
`/api/story/task/${taskId}/result`
);
return response.data;
}
/**
* Get cache statistics
*/
async getCacheStats(): Promise<{ success: boolean; stats: CacheStats }> {
const response = await pollingApiClient.get<{ success: boolean; stats: CacheStats }>(
"/api/story/cache/stats"
);
return response.data;
}
/**
* Clear the story generation cache
*/
async clearCache(): Promise<{ success: boolean; status: string; message: string }> {
const response = await pollingApiClient.post<{ success: boolean; status: string; message: string }>(
"/api/story/cache/clear"
);
return response.data;
}
/**
* Generate images for story scenes
*/
async generateSceneImages(request: StoryImageGenerationRequest): Promise<StoryImageGenerationResponse> {
const response = await aiApiClient.post<StoryImageGenerationResponse>(
"/api/story/generate-images",
request
);
return response.data;
}
/**
* Get image URL for a scene image
*/
getImageUrl(imageUrl: string): string {
// If imageUrl is already a full URL, return it as-is
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// Otherwise, prepend the base URL
const baseURL = aiApiClient.defaults.baseURL || '';
// Remove trailing slash from baseURL if present, and leading slash from imageUrl if present
const cleanBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const cleanImageUrl = imageUrl.startsWith('/') ? imageUrl : `/${imageUrl}`;
return `${cleanBaseURL}${cleanImageUrl}`;
}
/**
* Generate audio narration for story scenes
*/
async generateSceneAudio(request: StoryAudioGenerationRequest): Promise<StoryAudioGenerationResponse> {
const response = await aiApiClient.post<StoryAudioGenerationResponse>(
"/api/story/generate-audio",
request
);
return response.data;
}
/**
* Get audio URL for a scene audio file
*/
getAudioUrl(audioUrl: string): string {
// If audioUrl is already a full URL, return it as-is
if (audioUrl.startsWith('http://') || audioUrl.startsWith('https://')) {
return audioUrl;
}
// Otherwise, prepend the base URL
const baseURL = aiApiClient.defaults.baseURL || '';
// Remove trailing slash from baseURL if present, and leading slash from audioUrl if present
const cleanBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const cleanAudioUrl = audioUrl.startsWith('/') ? audioUrl : `/${audioUrl}`;
return `${cleanBaseURL}${cleanAudioUrl}`;
}
/**
* Generate video from story scenes, images, and audio
*/
async generateStoryVideo(request: StoryVideoGenerationRequest): Promise<StoryVideoGenerationResponse> {
const response = await aiApiClient.post<StoryVideoGenerationResponse>(
"/api/story/generate-video",
request
);
return response.data;
}
/**
* Generate video asynchronously (returns task_id for polling)
*/
async generateStoryVideoAsync(
request: StoryVideoGenerationRequest
): Promise<{ task_id: string; status: string; message: string }> {
const response = await aiApiClient.post<{ task_id: string; status: string; message: string }>(
"/api/story/generate-video-async",
request
);
return response.data;
}
/**
* Generate HD AI animation via Hugging Face text-to-video (server saves and returns url)
*/
async generateHdVideo(payload: {
prompt: string;
provider?: string;
model?: string;
num_frames?: number;
guidance_scale?: number;
num_inference_steps?: number;
negative_prompt?: string;
seed?: number;
}): Promise<{ success: boolean; video_filename: string; video_url: string; provider: string; model: string }> {
// Long-running request - use longRunningApiClient to allow more time
const { longRunningApiClient } = await import("../api/client");
const response = await longRunningApiClient.post(
"/api/story/hd-video",
{
provider: "huggingface",
...payload,
}
);
return response.data;
}
/**
* Generate HD AI animation asynchronously (returns task_id for polling)
*/
async generateHdVideoAsync(payload: {
prompt: string;
provider?: string;
model?: string;
num_frames?: number;
guidance_scale?: number;
num_inference_steps?: number;
negative_prompt?: string;
seed?: number;
}): Promise<{ task_id: string; status: string; message: string }> {
const response = await aiApiClient.post<{ task_id: string; status: string; message: string }>(
"/api/story/hd-video-async",
{
provider: "huggingface",
...payload,
}
);
return response.data;
}
/**
* Generate HD AI video for a single scene with AI-enhanced prompt
*/
async generateHdVideoScene(payload: {
scene_number: number;
scene_data: StoryScene;
story_context: Record<string, any>;
all_scenes: StoryScene[];
scene_image_url?: string;
provider?: string;
model?: string;
num_frames?: number;
guidance_scale?: number;
num_inference_steps?: number;
negative_prompt?: string;
seed?: number;
}): Promise<{
success: boolean;
scene_number: number;
video_filename: string;
video_url: string;
prompt_used: string;
provider: string;
model: string;
}> {
// Long-running request - use longRunningApiClient to allow more time
const { longRunningApiClient } = await import("../api/client");
const response = await longRunningApiClient.post(
"/api/story/hd-video-scene",
{
provider: "huggingface",
...payload,
}
);
return response.data;
}
/**
* Get video URL for a story video file
*/
getVideoUrl(videoUrl: string): string {
// If videoUrl is already a full URL, return it as-is
if (videoUrl.startsWith('http://') || videoUrl.startsWith('https://')) {
return videoUrl;
}
// Otherwise, prepend the base URL
const baseURL = aiApiClient.defaults.baseURL || '';
// Remove trailing slash from baseURL if present, and leading slash from videoUrl if present
const cleanBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const cleanVideoUrl = videoUrl.startsWith('/') ? videoUrl : `/${videoUrl}`;
return `${cleanBaseURL}${cleanVideoUrl}`;
}
}
export const storyWriterApi = new StoryWriterApi();