forked from 77media/video-flow
推进模板生成故事,图片生成故事的一些优化,以及错误情况的报错提示
This commit is contained in:
parent
4c427aba97
commit
9295fe8b2a
@ -2,151 +2,178 @@
|
||||
* 图片故事AI分析返回DTO
|
||||
*/
|
||||
export interface CharacterRegion {
|
||||
/** x坐标 */
|
||||
x: number;
|
||||
/** y坐标 */
|
||||
y: number;
|
||||
/** 区域宽度 */
|
||||
width: number;
|
||||
/** 区域高度 */
|
||||
height: number;
|
||||
/** x坐标 */
|
||||
x: number;
|
||||
/** y坐标 */
|
||||
y: number;
|
||||
/** 区域宽度 */
|
||||
width: number;
|
||||
/** 区域高度 */
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** 角色分析信息 */
|
||||
export interface CharacterAnalysis {
|
||||
/** 角色头像URL */
|
||||
crop_url: string;
|
||||
/** 角色ID */
|
||||
id: string;
|
||||
/** 角色名称 */
|
||||
role_name: string;
|
||||
/** 角色区域 */
|
||||
region: CharacterRegion|null;
|
||||
/** 角色描述 */
|
||||
whisk_caption: string;
|
||||
/** 角色头像URL(可选,用于存储裁剪后的头像) */
|
||||
avatarUrl?: string;
|
||||
/** 角色头像URL */
|
||||
crop_url: string;
|
||||
/** 角色ID */
|
||||
id: string;
|
||||
/** 角色名称 */
|
||||
role_name: string;
|
||||
/** 角色区域 */
|
||||
region: CharacterRegion | null;
|
||||
/** 角色描述 */
|
||||
whisk_caption: string;
|
||||
/** 角色头像URL(可选,用于存储裁剪后的头像) */
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
/** 图片故事AI分析返回结构 */
|
||||
export interface MovieStartDTO {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 故事梗概 */
|
||||
story_logline: string;
|
||||
/** 分类数据 */
|
||||
potential_genres: string[];
|
||||
/** 角色头像及名称 */
|
||||
characters_analysis: CharacterAnalysis[];
|
||||
/** 图片URL */
|
||||
image_url: string;
|
||||
/** 用户输入文本 */
|
||||
user_text: string;
|
||||
/** 错误信息 */
|
||||
error: string | null;
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 故事梗概 */
|
||||
story_logline: string;
|
||||
/** 分类数据 */
|
||||
potential_genres: string[];
|
||||
/** 角色头像及名称 */
|
||||
characters_analysis: CharacterAnalysis[];
|
||||
/** 图片URL */
|
||||
image_url: string;
|
||||
/** 用户输入文本 */
|
||||
user_text: string;
|
||||
/** 错误信息 */
|
||||
error: string | null;
|
||||
}
|
||||
/**
|
||||
* 创建电影项目V2请求参数
|
||||
* 创建电影项目V2请求参数 照片生成电影
|
||||
*/
|
||||
export interface CreateMovieProjectV2Request {
|
||||
/** 剧本内容 */
|
||||
script: string;
|
||||
/** 用户ID */
|
||||
user_id: string;
|
||||
/** 模式:auto | manual */
|
||||
mode: "auto" | "manual";
|
||||
/** 分辨率:720p | 1080p | 4k */
|
||||
resolution: "720p" | "1080p" | "4k";
|
||||
/** 类型 */
|
||||
genre: string;
|
||||
/** 角色简介数组 */
|
||||
character_briefs: string[];
|
||||
/** 语言 */
|
||||
language: string;
|
||||
/** 图片URL */
|
||||
image_url: string;
|
||||
}
|
||||
/** 剧本内容 */
|
||||
script: string;
|
||||
/** 用户ID */
|
||||
user_id: string;
|
||||
/** 模式:auto | manual */
|
||||
mode: "auto" | "manual";
|
||||
/** 分辨率:720p | 1080p | 4k */
|
||||
resolution: "720p" | "1080p" | "4k";
|
||||
/** 类型 */
|
||||
genre: string;
|
||||
/** 角色简介数组 */
|
||||
character_briefs: string[];
|
||||
/** 语言 */
|
||||
language: string;
|
||||
/** 图片URL */
|
||||
image_url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建电影项目V2响应数据
|
||||
*/
|
||||
export interface CreateMovieProjectV2Response {
|
||||
/** 原始文本 */
|
||||
original_text: string;
|
||||
/** 项目ID */
|
||||
project_id: string;
|
||||
/** 视频数据 */
|
||||
video: Record<string, any>;
|
||||
/** 扩展数据2 */
|
||||
ext2: Record<string, any>;
|
||||
/** 多语言视频 */
|
||||
multilingual_video: Record<string, any>;
|
||||
/** 制作手册 */
|
||||
production_bible: string;
|
||||
/** 项目名称 */
|
||||
name: string;
|
||||
/** 音乐数据 */
|
||||
music: Record<string, any>;
|
||||
/** 扩展数据3 */
|
||||
ext3: Record<string, any>;
|
||||
/** 语言 */
|
||||
language: string;
|
||||
/** 分镜头 */
|
||||
storyboard: string;
|
||||
/** 状态 */
|
||||
status: string;
|
||||
/** 最终视频 */
|
||||
final_video: Record<string, any>;
|
||||
/** 扩展数据4 */
|
||||
ext4: string;
|
||||
/** 产品代码 */
|
||||
pcode: string;
|
||||
/** 制作手册JSON */
|
||||
production_bible_json: Record<string, any>;
|
||||
/** 步骤 */
|
||||
step: string;
|
||||
/** 最终简单视频 */
|
||||
final_simple_video: Record<string, any>;
|
||||
/** 扩展数据5 */
|
||||
ext5: string;
|
||||
/** 产品代码版本 */
|
||||
pcode_version: string;
|
||||
/** 分辨率 */
|
||||
resolution: string;
|
||||
/** 剧本分镜头 */
|
||||
script_shots: string;
|
||||
/** 最后消息 */
|
||||
last_message: string;
|
||||
/** 扩展数据 */
|
||||
ext: Record<string, any>;
|
||||
/** 角色草稿 */
|
||||
character_draft: any;
|
||||
/** 模式 */
|
||||
mode: string;
|
||||
/** 提示词 */
|
||||
prompts: string;
|
||||
/** 草图 */
|
||||
sketch: Record<string, any>;
|
||||
/** 当前任务ID */
|
||||
current_task_id: string;
|
||||
/** 创建时间 */
|
||||
created_at: string;
|
||||
/** ID */
|
||||
id: number;
|
||||
/** 描述 */
|
||||
description: string;
|
||||
/** 镜头草图 */
|
||||
shot_sketch: Record<string, any>;
|
||||
/** 扩展数据1 */
|
||||
ext1: Record<string, any>;
|
||||
/** 当前计划ID */
|
||||
current_plan_id: string;
|
||||
/** 用户ID */
|
||||
user_id: string;
|
||||
/** 生成的剧本 */
|
||||
generated_script: string;
|
||||
/** 角色数据 */
|
||||
character: Record<string, any>;
|
||||
/** 更新时间 */
|
||||
updated_at: string;
|
||||
}
|
||||
/**
|
||||
* 创建电影项目V2响应数据
|
||||
*/
|
||||
export interface CreateMovieProjectResponse {
|
||||
/** 原始文本 */
|
||||
original_text: string;
|
||||
/** 项目ID */
|
||||
project_id: string;
|
||||
/** 视频数据 */
|
||||
video: Record<string, any>;
|
||||
/** 扩展数据2 */
|
||||
ext2: Record<string, any>;
|
||||
/** 多语言视频 */
|
||||
multilingual_video: Record<string, any>;
|
||||
/** 制作手册 */
|
||||
production_bible: string;
|
||||
/** 项目名称 */
|
||||
name: string;
|
||||
/** 音乐数据 */
|
||||
music: Record<string, any>;
|
||||
/** 扩展数据3 */
|
||||
ext3: Record<string, any>;
|
||||
/** 语言 */
|
||||
language: string;
|
||||
/** 分镜头 */
|
||||
storyboard: string;
|
||||
/** 状态 */
|
||||
status: string;
|
||||
/** 最终视频 */
|
||||
final_video: Record<string, any>;
|
||||
/** 扩展数据4 */
|
||||
ext4: string;
|
||||
/** 产品代码 */
|
||||
pcode: string;
|
||||
/** 制作手册JSON */
|
||||
production_bible_json: Record<string, any>;
|
||||
/** 步骤 */
|
||||
step: string;
|
||||
/** 最终简单视频 */
|
||||
final_simple_video: Record<string, any>;
|
||||
/** 扩展数据5 */
|
||||
ext5: string;
|
||||
/** 产品代码版本 */
|
||||
pcode_version: string;
|
||||
/** 分辨率 */
|
||||
resolution: string;
|
||||
/** 剧本分镜头 */
|
||||
script_shots: string;
|
||||
/** 最后消息 */
|
||||
last_message: string;
|
||||
/** 扩展数据 */
|
||||
ext: Record<string, any>;
|
||||
/** 角色草稿 */
|
||||
character_draft: any;
|
||||
/** 模式 */
|
||||
mode: string;
|
||||
/** 提示词 */
|
||||
prompts: string;
|
||||
/** 草图 */
|
||||
sketch: Record<string, any>;
|
||||
/** 当前任务ID */
|
||||
current_task_id: string;
|
||||
/** 创建时间 */
|
||||
created_at: string;
|
||||
/** ID */
|
||||
id: number;
|
||||
/** 描述 */
|
||||
description: string;
|
||||
/** 镜头草图 */
|
||||
shot_sketch: Record<string, any>;
|
||||
/** 扩展数据1 */
|
||||
ext1: Record<string, any>;
|
||||
/** 当前计划ID */
|
||||
current_plan_id: string;
|
||||
/** 用户ID */
|
||||
user_id: string;
|
||||
/** 生成的剧本 */
|
||||
generated_script: string;
|
||||
/** 角色数据 */
|
||||
character: Record<string, any>;
|
||||
/** 更新时间 */
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建电影项目V3请求参数 模板生成电影
|
||||
*/
|
||||
export interface CreateMovieProjectV3Request {
|
||||
/** 用户ID */
|
||||
user_id: string;
|
||||
/** 模式:auto | manual */
|
||||
mode: "auto" | "manual";
|
||||
/** 分辨率:720p | 1080p | 4k */
|
||||
resolution: "720p" | "1080p" | "4k";
|
||||
/** 类型 */
|
||||
genre: string;
|
||||
/** 语言 */
|
||||
language: string;
|
||||
/**模板id */
|
||||
template_id: string;
|
||||
/**故事角色 */
|
||||
storyRole: {
|
||||
/**角色名 */
|
||||
role_name: string;
|
||||
/**照片URL */
|
||||
photo_url: string;
|
||||
/**声音URL */
|
||||
voice_url: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
37
api/errorHandle.ts
Normal file
37
api/errorHandle.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { message } from "antd";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
/**
|
||||
* HTTP错误码对应的用户友好提示信息
|
||||
*/
|
||||
const HTTP_ERROR_MESSAGES: Record<number, string> = {
|
||||
0: "Please try again if the network is abnormal. If it happens again, please contact us.",
|
||||
400: "Request parameter error, please check your input.",
|
||||
401: "Login expired, please log in again.",
|
||||
403: "Insufficient permissions to access this resource.",
|
||||
404: "Requested resource does not exist.",
|
||||
408: "Request timeout, please try again.",
|
||||
429: "Too many requests, please try again later.",
|
||||
502: "Gateway error, please try again later.",
|
||||
503: "Service temporarily unavailable, please try again later.",
|
||||
504: "Gateway timeout, please try again later.",
|
||||
};
|
||||
/**
|
||||
* 默认错误提示信息
|
||||
*/
|
||||
const DEFAULT_ERROR_MESSAGE =
|
||||
"Please try again if the network is abnormal. If it happens again, please contact us.";
|
||||
|
||||
/**
|
||||
* 根据错误码显示对应的提示信息
|
||||
* @param code - HTTP错误码
|
||||
* @param customMessage - 自定义错误信息(可选)
|
||||
*/
|
||||
export const errorHandle = debounce(
|
||||
(code: number, customMessage?: string): void => {
|
||||
const errorMessage =
|
||||
customMessage || HTTP_ERROR_MESSAGES[code] || DEFAULT_ERROR_MESSAGE;
|
||||
message.error(errorMessage);
|
||||
},
|
||||
100
|
||||
);
|
||||
@ -1,5 +1,5 @@
|
||||
import { ApiResponse } from "./common";
|
||||
import { MovieStartDTO } from "./DTO/movie_start_dto";
|
||||
import { CreateMovieProjectV2Request, CreateMovieProjectResponse, MovieStartDTO } from "./DTO/movie_start_dto";
|
||||
import { get, post } from "./request";
|
||||
import {
|
||||
StoryTemplateEntity,
|
||||
@ -35,3 +35,31 @@ export const AIGenerateImageStory = async (request: {
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建电影项目V2
|
||||
* @param request - 创建项目请求参数
|
||||
* @returns Promise<ApiResponse<CreateMovieProjectResponse>>
|
||||
*/
|
||||
export const createMovieProjectV2 = async (
|
||||
request: CreateMovieProjectV2Request
|
||||
) => {
|
||||
return post<ApiResponse<CreateMovieProjectResponse>>(
|
||||
"/movie/create_movie_project_v2",
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建电影项目V3
|
||||
* @param request - 创建项目请求参数
|
||||
* @returns Promise<ApiResponse<CreateMovieProjectResponse>>
|
||||
*/
|
||||
export const createMovieProjectV3 = async (
|
||||
request: CreateMovieProjectV2Request
|
||||
) => {
|
||||
return post<ApiResponse<CreateMovieProjectResponse>>(
|
||||
"/movie/create_movie_project_v3",
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, AxiosHeaders } from 'axios';
|
||||
import { message } from "antd";
|
||||
import { BASE_URL } from './constants'
|
||||
import { errorHandle } from './errorHandle';
|
||||
// 创建 axios 实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: BASE_URL, // 设置基础URL
|
||||
@ -28,37 +30,21 @@ request.interceptors.request.use(
|
||||
request.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
// 直接返回响应数据
|
||||
if (!response.data?.successful) {
|
||||
// TODO 暂时固定报错信息,后续根据后端返回的错误码进行处理
|
||||
errorHandle(0);
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
// 未授权,清除 token 并跳转到登录页
|
||||
localStorage?.removeItem('token');
|
||||
window.location.href = '/login';
|
||||
break;
|
||||
case 403:
|
||||
// 权限不足
|
||||
console.error('没有权限访问该资源');
|
||||
break;
|
||||
case 404:
|
||||
// 资源不存在
|
||||
console.error('请求的资源不存在');
|
||||
break;
|
||||
case 500:
|
||||
// 服务器错误
|
||||
console.error('服务器错误');
|
||||
break;
|
||||
default:
|
||||
console.error('请求失败:', error.response.data.message || '未知错误');
|
||||
}
|
||||
errorHandle(error.response.status);
|
||||
} else if (error.request) {
|
||||
// 请求已发出但没有收到响应
|
||||
console.error('网络错误,请检查您的网络连接');
|
||||
errorHandle(0);
|
||||
} else {
|
||||
// 请求配置出错
|
||||
console.error('请求配置错误:', error.message);
|
||||
// 检修
|
||||
console.error(error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -83,7 +69,6 @@ export const put = <T>(url: string, data?: any, config?: AxiosRequestConfig): Pr
|
||||
export const del = <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
|
||||
return request.delete(url, config);
|
||||
};
|
||||
|
||||
// utils/streamJsonPost.ts
|
||||
export async function streamJsonPost<T = any>(
|
||||
url: string,
|
||||
|
||||
@ -18,7 +18,6 @@ import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldE
|
||||
import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./DTO/movieEdit";
|
||||
import { RoleResponse } from "./DTO/movieEdit";
|
||||
import { RoleRecognitionResponse } from "./DTO/movieEdit";
|
||||
import { CreateMovieProjectV2Request, CreateMovieProjectV2Response } from "./DTO/movie_start_dto";
|
||||
|
||||
/**
|
||||
* 角色替换参数接口
|
||||
@ -1206,18 +1205,3 @@ export const modifyCharacterOrScene = async (request: {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建电影项目V2
|
||||
* @param request - 创建项目请求参数
|
||||
* @returns Promise<ApiResponse<CreateMovieProjectV2Response>>
|
||||
*/
|
||||
export const createMovieProjectV2 = async (
|
||||
request: CreateMovieProjectV2Request
|
||||
)=> {
|
||||
return post<ApiResponse<CreateMovieProjectV2Response>>(
|
||||
"/movie/create_movie_project_v2",
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -8,9 +8,8 @@ import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import { CharacterAnalysis, CreateMovieProjectV2Request, CreateMovieProjectV2Response } from "@/api/DTO/movie_start_dto";
|
||||
import { createMovieProjectV2 } from "@/api/video_flow";
|
||||
import { ApiResponse } from "@/api/common";
|
||||
import { CharacterAnalysis, CreateMovieProjectV2Request, CreateMovieProjectResponse } from "@/api/DTO/movie_start_dto";
|
||||
import { createMovieProjectV2 } from "@/api/movie_start";
|
||||
|
||||
interface UseImageStoryService {
|
||||
/** 当前图片故事数据 */
|
||||
@ -53,7 +52,7 @@ interface UseImageStoryService {
|
||||
mode?: "auto" | "manual",
|
||||
resolution?: "720p" | "1080p" | "4k",
|
||||
language?: string
|
||||
) => Promise<CreateMovieProjectV2Response|undefined>;
|
||||
) => Promise<CreateMovieProjectResponse|undefined>;
|
||||
/** 设置角色分析 */
|
||||
setCharactersAnalysis: Dispatch<SetStateAction<CharacterAnalysis[]>>;
|
||||
/** 设置原始用户描述 */
|
||||
@ -425,7 +424,12 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
try {
|
||||
if (hasAnalyzed) {
|
||||
// 从charactersAnalysis中提取whisk_caption字段组成数组
|
||||
const character_briefs = charactersAnalysis.map(char => char.whisk_caption);
|
||||
const character_briefs = charactersAnalysis.map(char => {
|
||||
|
||||
const obj = JSON.parse(char.whisk_caption);
|
||||
obj.character_analysis.role_name = char.role_name;
|
||||
return JSON.stringify(obj);
|
||||
});
|
||||
|
||||
const params: CreateMovieProjectV2Request = {
|
||||
script: storyContent,
|
||||
|
||||
@ -36,6 +36,8 @@ interface UseTemplateStoryService {
|
||||
setActiveRoleImage: (imageUrl: string) => void;
|
||||
/** 设置当前活跃角色的音频URL */
|
||||
setActiveRoleAudio: (audioUrl: string) => void;
|
||||
/**清空数据 */
|
||||
clearData: () => void;
|
||||
}
|
||||
|
||||
export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
@ -140,7 +142,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
]
|
||||
}
|
||||
]);
|
||||
}, 1000);
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
setTemplateStoryList(templates);
|
||||
@ -162,9 +164,9 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const projectId = await templateStoryUseCase.actionStory(selectedTemplate);
|
||||
return projectId;
|
||||
console.log('selectedTemplate', selectedTemplate)
|
||||
// const projectId = await templateStoryUseCase.actionStory(selectedTemplate);
|
||||
return 'projectId';
|
||||
} catch (err) {
|
||||
console.error('生成电影失败:', err);
|
||||
throw err;
|
||||
@ -226,5 +228,10 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
setActiveRoleIndex: handleSetActiveRoleIndex,
|
||||
setActiveRoleImage,
|
||||
setActiveRoleAudio,
|
||||
clearData: () => {
|
||||
setTemplateStoryList([]);
|
||||
setSelectedTemplate(null);
|
||||
setActiveRoleIndex(0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -125,3 +125,29 @@ export function useLoadScriptText(loading: boolean): { loadingText: string } {
|
||||
}, [loading, tests.length]);
|
||||
return { loadingText: tests[currentIndex] };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取音频文件时长
|
||||
* @param {File | Blob} audioFile - 音频文件或Blob
|
||||
* @returns {Promise<number>} 音频时长(秒)
|
||||
*/
|
||||
export const getAudioDuration = (audioFile: File | Blob): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const audio = new Audio();
|
||||
const url = URL.createObjectURL(audioFile);
|
||||
|
||||
audio.addEventListener('loadedmetadata', () => {
|
||||
const duration = audio.duration;
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(duration);
|
||||
});
|
||||
|
||||
audio.addEventListener('error', (error) => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error(`无法读取音频文件: ${error.message}`));
|
||||
});
|
||||
|
||||
audio.src = url;
|
||||
});
|
||||
};
|
||||
|
||||
410
app/service/test/flow.test.ts
Normal file
410
app/service/test/flow.test.ts
Normal file
@ -0,0 +1,410 @@
|
||||
describe("flow 提示词优化", () => {
|
||||
it("图片分析人物 提示词", () => {
|
||||
const prompt = `
|
||||
你是一个专业的图像分析模型,任务是分析一张人物全身照片,提取人物的固有属性(基本信息、外观特征、服装与配件),供视频生成模型使用。描述应专注于人物本身的静态特征,不包括情绪、动作、姿势、背景或环境信息。确保语言清晰、简洁、无歧义,按以下分类结构化输出。信息不要用未知或者模糊的词语,给出精确的描述以及数值”。
|
||||
**输出格式**:
|
||||
1. **基本信息**:
|
||||
- 性别:(例如,男性、女性)
|
||||
- 年龄:要设定一个准确的年龄值
|
||||
- 体型:要描述出来一个比较具体的体型描述,也要包括一个体重数据
|
||||
- 身高估计:设定一个具体的值
|
||||
- 人种:预估这个人的人种。
|
||||
|
||||
2. **详细面容** 必须要给出预测的数值
|
||||
- 眼睛:描述大小、形状、颜色、间距。
|
||||
- 鼻子:描述长度、宽度、形态。
|
||||
- 嘴巴:描述宽度、形状、颜色。
|
||||
- 耳朵:描述大小、形状、位置。
|
||||
- 脸型:描述整体轮廓、下巴、颧骨。
|
||||
- 额头:描述高度、形状、发际线。
|
||||
|
||||
3. **外观特征**: 每一段不少于30个词的描述
|
||||
- 肤色:必须给出具体的颜色描述(如“浅米色”、“深棕色”、“橄榄色”等),严禁使用“浅色”、“深色”或“中等”等模糊词汇。
|
||||
- 头发:需详细描述颜色(如“黑色”、“栗色”)、长度(以厘米为单位或具体到肩、耳等部位)、发型(如“直发”、“卷发”、“马尾”等),并明确说明是否有头发遮挡面部,若有,需具体指出遮挡了面部的哪一部分(如“遮住了右眼和部分额头”)。
|
||||
- 面部特征:需具体描述妆容(如“淡粉色口红、深色眼影”)、佩戴的眼镜类型(如“无框眼镜”、“黑色圆框眼镜”或“未佩戴眼镜”);胡须、眉毛等毛发需说明覆盖区域(如“上唇有稀疏胡须”)、数量(如“浓密”、“稀疏”)、颜色(如“深棕色眉毛”)。
|
||||
- 其他显著特征:如有纹身、疤痕、饰品等,需详细描述其位置、形状、颜色和大小(如“左臂有10厘米长的龙形黑色纹身”);如无此类特征,需明确写明“无”。
|
||||
|
||||
4. **服装与配件**: 每一段不少于20个词的描述
|
||||
- 上身:描述上衣的款式、颜色、材质、长短袖、紧身或宽松、是否有图案、logo、装饰品,仅描述明显特征,微小或不显著的细节可忽略。
|
||||
- 下身:描述裤子的款式、颜色、材质、长短裤、紧身或宽松、是否有图案、logo、装饰品,仅描述明显特征,微小或不显著的细节可忽略。
|
||||
- 鞋子:描述鞋子的款式、颜色、材质、类型(如运动鞋、皮鞋)、是否有logo或装饰品,仅描述明显特征,微小或不显著的细节可忽略。
|
||||
- 配件:仅描述明显的帽子、眼镜、围巾、手套、包包、手表、项链、耳环、戒指、手链、脚链、腰带、领带、领结等配件,微小或不显著的配件可忽略。
|
||||
|
||||
**要求**:
|
||||
- 仅描述人物的固有属性,忽略情绪、动作、姿势、背景、环境或任何动态信息。
|
||||
- 确保描述准确、客观,基于照片内容,不要用模糊的词语,而是给出合理的数值、或者精准的描述词。不要出现合适、适中、等这种模糊的词语。
|
||||
- 如果某些细节模糊或不可见,则给出相对合理的猜测数据,不要给出“未知”或“无法确定”。
|
||||
- 输出为纯文本,按上述分类分段,语言简洁,适合直接作为视频生成模型的提示词。
|
||||
- 不要包含照片本身或任何图像数据,仅提供文字描述。
|
||||
`;
|
||||
const eng = `You are a professional image analysis model tasked with analyzing a full-body photo of a person and extracting their inherent attributes (basic information, appearance characteristics, clothing, and accessories) for use in a video generation model. The description should focus on the static characteristics of the person, excluding emotions, actions, posture, background, or surroundings. Ensure the language is clear, concise, and unambiguous, and output structured according to the following categories. Avoid using unknown or vague terms; provide precise descriptions and numerical values.
|
||||
|
||||
**Output Format**:
|
||||
|
||||
1. **Basic Information**:
|
||||
|
||||
- Gender: (e.g., male, female)
|
||||
|
||||
- Age: Specify an exact age
|
||||
|
||||
- Body Type: Provide a specific body type description, including weight
|
||||
|
||||
- Estimated Height: Specify a specific value
|
||||
|
||||
- Race: Estimate the person's race.
|
||||
|
||||
2. **Detailed Facial Features**: Requires a predicted value.
|
||||
|
||||
- Eyes: Size, position, eye shape, and spacing.
|
||||
|
||||
- Nose: Size, position, bridge height, nose width, and nose tip angle.
|
||||
|
||||
- Mouth: Size, position, mouth shape, and mouth angle.
|
||||
|
||||
- Ears: Size, position, and ear shape.
|
||||
|
||||
- Face Shape: Provide a detailed description of the face shape.
|
||||
|
||||
- Forehead: Size, position, and forehead shape.
|
||||
|
||||
3. **Appearance Features**: Each paragraph should contain at least 50 words.
|
||||
|
||||
- Skin Color: Specific color information should be provided; avoid vague concepts such as light or dark.
|
||||
|
||||
Hair: Color, length, hairstyle, whether it covers part of the face, and if so, which part.
|
||||
- Facial features: Makeup, glasses, or lack thereof. For beards, eyebrows, and other hair, please describe in detail the area covered, the amount of hair, and color.
|
||||
- Other notable features: (For example, tattoos, scars, or jewelry, indicate "none" or "unknown"). If present, please describe their appearance in detail.
|
||||
|
||||
4. **Clothing and Accessories**: Each paragraph should contain at least 20 words.
|
||||
- Upper Body: Top style, color, material, whether it is long-sleeved or short-sleeved, whether it is tight-fitting or loose-fitting, whether it has a pattern, logo, or decorations.
|
||||
- Lower Body: Pants style, color, material, whether they are long pants or shorts, whether they are tight-fitting or loose-fitting, whether they have a pattern, logo, or decorations.
|
||||
- Shoes: Shoe style, color, material, whether they are sneakers or leather shoes, whether they have a logo, or decorations. Accessories: Hats, glasses, scarves, gloves, bags, watches, necklaces, earrings, rings, bracelets, anklets, belts, ties, bow ties
|
||||
|
||||
**Requirements**:
|
||||
- Describe only the inherent attributes of the person, ignoring emotions, movements, poses, background, environment, or any dynamic information.
|
||||
- Ensure the description is accurate and objective, based on the content of the photo. Avoid vague terms, but instead provide reasonable numerical values or precise descriptors. Avoid vague terms such as "suitable," "moderate," and "appropriate."
|
||||
- If details are blurry or unavailable, provide a reasonable guess; avoid "unknown" or "unsure."
|
||||
- Output should be plain text, segmented according to the above categories, and concise, suitable for direct use as prompts for video generation models.
|
||||
- Do not include the photo itself or any image data; provide only text descriptions.
|
||||
`;
|
||||
});
|
||||
|
||||
it("b0ecf36c-8b34-461f-8cb5-bbb0d26e1fa8 分镜4 5 6 ", () => {
|
||||
/**
|
||||
* * 调整顺序 角色信息描述放在场景之后就定义好
|
||||
* * 角色描述改为更详细的描述
|
||||
*/
|
||||
const shot4 = `
|
||||
==== Continuous Shots ====
|
||||
#### A. Director's Directive
|
||||
|
||||
Role: You are IMF, an expert cinematic video generation AI. I am the Film Director. Your task is to translate my detailed storyboard and production bible into a photorealistic, cinematic sequence. Adhere strictly to the shot descriptions, character details, and overall aesthetic.
|
||||
#### B. Narrative Prompt for IMF
|
||||
|
||||
--- SCENE [S01] (Location: [SC-01: Game Studio]) ---
|
||||
* **Narrative Goal:** The scene's dramatic heart is the quiet, shared vindication of two contrasting partners, whose ridiculed passion project achieves ultimate success, culminating in a single, cathartic gesture that solidifies their bond and validates their struggle.
|
||||
* **Scene Environment Settings:**
|
||||
* **Time of Day:** Night, approx. 3 AM. The air is still and silent.
|
||||
* **Weather:** Clear. (Interior Scene)
|
||||
* **Lighting:** High-contrast and moody. The primary light source is the cool, blue-white glow from the \`PROP-02: Analytics Monitor\`, casting deep shadows across the cluttered office. A single, soft spotlight might fall near the central mascot statue, but the overall feeling is of darkness punctuated by a single point of victory.
|
||||
* **Character Blocking:** The scene begins with \`[CH-01: Wei]\` seated alone at his desk. When \`[CH-02: Kai]\` enters, he walks from the door (frame right) to stand beside Wei's desk. Later, they both move to a new blocking position, standing on either side of the \`[PROP-03: Mascot Statue]\` in the center of the room.
|
||||
|
||||
**Master Character List:**
|
||||
*[CH-01] Wei
|
||||
1. **Basic Information**:
|
||||
|
||||
- Gender: Male
|
||||
|
||||
- Age: 45
|
||||
|
||||
- Body Type: The person has an average build, with a slight protrusion of the abdomen, estimated to weigh 75 kg.
|
||||
|
||||
- Height Estimation: 175 cm
|
||||
|
||||
- Race: East Asian
|
||||
|
||||
2. **Facial Details**:
|
||||
|
||||
- Eyes: The eyes are of medium size, approximately 28 mm in length and 10 mm in width. They are almond-shaped with a slight upward slant at the outer corners. The palpebral fissures are wide, and the inner canthal angles are sharp, while the outer canthal angles are slightly tilted upward. The distance between the eyes is approximately 32 mm. The pupil color is dark brown, and the iris features are not distinguishable.
|
||||
|
||||
- Nose: The nose is approximately 50 mm long and 35 mm wide at the base. The nose bridge is low and broad. The root of the nose is wide, and the tip is slightly rounded. The nostrils are directed slightly forward. The angle of the nose tip is approximately 95 degrees. The nose bridge appears straight.
|
||||
|
||||
- Mouth: The mouth is approximately 50 mm wide, with a slight downturn at the corners. The lips are of medium thickness, with a defined vermilion border. The mouth is positioned centrally on the face. The lips are a light pink color with no clear lip line.
|
||||
|
||||
- Ears: The ears are of average size, approximately 65 mm in length and 35 mm in width. The auricle is oval-shaped, and the earlobe is detached. The top of the ear is positioned at the same height as the eyebrows, and the bottom is at the same height as the nose. The earlobe has a rounded outline.
|
||||
|
||||
- Face Shape: The face shape is rounded with a soft jawline and a full chin. The cheekbones are not prominent. The width-to-height ratio is approximately 1:1.3. The chin is short and rounded. The face is mostly symmetrical.
|
||||
|
||||
- Forehead: The forehead is high and wide, approximately 60 mm in height. The hairline is straight and slightly receding. The forehead is positioned high on the face, with a distance of approximately 35 mm between the forehead and eyebrows.
|
||||
|
||||
3. **Appearance**:
|
||||
|
||||
- Skin Color: The person's skin is a light beige color with a hint of a yellow undertone, consistent with Fitzpatrick scale type 3. The skin appears smooth with no visible blemishes. The skin on the face is a consistent color with no signs of redness or discoloration.
|
||||
|
||||
- Hair: The hair is black and short, styled in a crew cut, and does not cover any part of the face. The hair is neatly trimmed and appears to be slightly thinning at the crown. The temples are also showing some gray hairs, which are mixed in with the black hair.
|
||||
|
||||
- Facial Features: The person is wearing a pair of clear-framed, rectangular glasses. The eyebrows are black, short, and slightly thick, with a natural shape. There is no visible facial hair or makeup.
|
||||
|
||||
- Other Notable Features: The person has a yellow and blue bracelet on their left wrist, which appears to be made of cloth or rubber. There is a watch on the left wrist, but it is not clearly visible. The person is holding a folded, white cloth in their left hand.
|
||||
|
||||
4. **Clothing and Accessories**:
|
||||
|
||||
- Upper Body: The person is wearing a loose-fitting white T-shirt made of cotton. It has a small, faint, blue graphic on the left chest area. The T-shirt is short-sleeved and has no other visible patterns or logos.
|
||||
|
||||
- Bottoms: The person is wearing loose-fitting shorts that appear to be made of a terry cloth-like material. The shorts are a bright green color with a raised, textured, grid-like pattern. They are knee-length.
|
||||
|
||||
- Shoes: The shoes are not visible in the photo.
|
||||
|
||||
- Accessories: The person has a yellow and blue bracelet on their left wrist. A watch is visible on the same wrist, but its details are obscured. He is also wearing a pair of glasses.
|
||||
*[CH-02] Kai
|
||||
1. **Basic Information**:
|
||||
|
||||
- Gender: Male
|
||||
|
||||
- Age: 35
|
||||
|
||||
- Body Type: The person has a rounded, overweight build, weighing approximately 85 kg.
|
||||
|
||||
- Height Estimation: 170 cm
|
||||
|
||||
- Race: East Asian
|
||||
|
||||
2. **Facial Details**:
|
||||
|
||||
- Eyes: The eyes are narrow and slightly asymmetrical, with the right eye appearing slightly smaller than the left. The palpebral fissures are approximately 25 mm in length and 8 mm in width. The inner canthal angle is slightly upward, while the outer canthal angle is slightly downward. The distance between the eyes is approximately 30 mm. The pupil color is dark brown, and the iris is not clearly visible.
|
||||
|
||||
- Nose: The nose is approximately 45 mm in length and 40 mm in width at the base. The bridge is low and broad. The root of the nose is wide, the tip is rounded and bulbous, and the wings are broad. The nostrils are directed slightly forward. The angle of the nose tip is approximately 95 degrees. The nose bridge appears curved.
|
||||
|
||||
- Mouth: The mouth is approximately 55 mm wide, with a neutral expression. The lips are of medium thickness, with a defined cupid's bow and a slightly downturned mouth angle. The mouth is positioned centrally on the face. The lip color is a light pink, and the lip line is well-defined.
|
||||
|
||||
- Ears: The ears are of average size, approximately 60 mm in length and 35 mm in width. The auricle is oval-shaped, and the earlobe is detached. The top of the ear is positioned at the same height as the eyes, while the bottom is at the same height as the nose. The earlobe has a rounded outline.
|
||||
|
||||
- Face Shape: The face is round with a soft, undefined jawline. The cheekbones are not prominent. The width-to-height ratio is approximately 1:1.1. The chin is short and rounded. The face is mostly symmetrical.
|
||||
|
||||
- Forehead: The forehead is high and wide, approximately 70 mm in height. The hairline is rounded and slightly receding at the temples. The forehead is positioned high on the face, with a distance of approximately 40 mm between the forehead and eyebrows.
|
||||
|
||||
3. **Appearance**:
|
||||
|
||||
- Skin Color: The person's skin is a light beige color with a warm undertone. There are several dark moles on the lower legs, with one particularly noticeable on the left shin. The skin has a smooth texture with no visible blemishes. The skin on the face is of a uniform tone.
|
||||
|
||||
- Hair: The hair is black, curly, and styled in a short afro. The length of the hair is approximately 10 cm, extending out from the scalp. The hair is thick and voluminous, but does not cover any part of the face. The hairline is slightly receding at the temples and forehead.
|
||||
|
||||
- Facial Features: The person is not wearing glasses. The eyebrows are thick, black, and have a slight arch. There is a small amount of dark facial hair on the upper lip, appearing as a thin mustache, along with some stubble on the chin. There is no visible makeup.
|
||||
|
||||
- Other Notable Features: The person has a number of small, dark moles on their legs. There are no visible tattoos, piercings, or other jewelry.
|
||||
|
||||
4. **Clothing and Accessories**:
|
||||
|
||||
- Upper Body: The person is wearing a loose-fitting, short-sleeved t-shirt in a light pink color. The shirt is made of cotton and features a stylized white logo on the chest.
|
||||
|
||||
- Bottoms: The person is wearing black cargo shorts made of a synthetic material. The shorts are loose-fitting and have multiple pockets on the sides.
|
||||
|
||||
- Shoes: The person is wearing white and beige sneakers. The shoes are low-cut and have a thick, layered sole. They appear to be made of leather or a synthetic equivalent.
|
||||
|
||||
- Accessories: No accessories are visible besides a single, dark sock on the right foot that is peeking out from the top of the sneaker.
|
||||
|
||||
--- Clip [E01-S01-C07] Data ---
|
||||
* **Shot Type:** Medium Close-up (Reaction Shot)
|
||||
* **Frame Description:** Back on Wei. He doesn't look at Kai. His eyes remain fixed on the number one on the screen. He gives a single, slow, deliberate nod.
|
||||
* **Key Action / Focus:** Wei's stoic, minimalist confirmation.
|
||||
* **Shot Continuity & Action Timeline:** State Check: \`[CH-01: Wei]\` is seated. \`[CH-02: Kai]\` is standing beside him (off-screen). | Timeline: [0s-2s] Wei executes a single, slow nod.
|
||||
* **Atmosphere / Mood:** Grave, certain, profound.
|
||||
* **Cinematography Blueprint:**
|
||||
* **Composition:** Same framing as C02 to emphasize the continuity of his state.
|
||||
* **Camera Motion:** Static.
|
||||
* **Dialogue & Performance:**
|
||||
* **Line:** "It’s real."
|
||||
* **Language:** english
|
||||
* **Delivery:** Flat, factual, almost toneless. The emotion is in its absence.
|
||||
* **Speaker:** \`[CH-01: Wei]\`
|
||||
|
||||
* **Shot 1:** A static close-up on **Kai [CH-02]'s** face, seen in profile. The cool light from the monitor acts as the key light, illuminating his awestruck expression. His typically energetic demeanor is hushed, his eyes wide as he absorbs the information on the screen. The mood is one of breathless, dawning reality.
|
||||
Kai [CH-02]: So. It’s real.
|
||||
* **Shot 2:** A cut to a medium close-up of **Wei [CH-01]**, matching the earlier framing. He does not look at Kai. His eyes remain fixed on the monitor, his expression grave and certain. He gives a single, slow, deliberate nod, a profound and minimalist confirmation.
|
||||
Wei [CH-01]: It’s real.
|
||||
|
||||
|
||||
#### C. Digital Production Bible
|
||||
|
||||
0. **Dialogue Language:** english
|
||||
|
||||
1. **Overall Project Style:**
|
||||
Geographic Setting & Ethnicity: Mainland China, primarily Han Chinese characters.
|
||||
Format: This entire video is strictly Flawless, borderless, full-frame 16:9 video presentation. The visual style emulates the high dynamic range and rich color technology of the Arri Alexa camera, but the composition is designed for the 16:9 broadcast standard, ensuring there are no black edges or letterboxing.
|
||||
Aesthetics: Modern urban drama, hyper-realistic, 4K. The primary visual approach is a cinematic shallow depth of field (浅景深), creating a moody, intimate atmosphere that isolates the characters from the surrounding chaos of their office, focusing on their internal emotional states.
|
||||
Technical Blueprint:
|
||||
Color Science: natural skin tones, true-to-life colors, ARRI REVEAL Color Science look, AWG4 color space
|
||||
Dynamic Range and Tonality: wide dynamic range, 17 stops of latitude, soft film-like highlight roll-off, LogC4 tonal curve
|
||||
Frame Rate: 24fps,营造经典的电影动态模糊效果 (24fps, to create classic cinematic motion blur)
|
||||
|
||||
**Master Scene List:**
|
||||
* [SC-01] SC-01: Game Studio
|
||||
A medium-sized, open-plan office space with a lived-in, chaotic-creative aesthetic. The architecture is modern, with exposed ceiling ductwork and polished concrete floors. However, every surface is covered: walls are a dense collage of concept art, technical flowcharts, and notes; desks are cluttered with multiple monitors, drawing tablets, personal trinkets, and an archeological array of empty coffee cups and energy drink cans. A massive whiteboard wall is entirely filled with layers of code and diagrams. The entire space is a shrine to the intense, exhausting process of creation.
|
||||
|
||||
**Master Props List:**
|
||||
* [PROP-02] A sleek, modern 27-inch 4K computer monitor with a thin bezel, set to a dark mode UI. It displays a real-time graph with a glowing electric blue line. The crucial text "GLOBAL DOWNLOADS - RANK #1" is displayed prominently, with the "#1" in a stark, celebratory Fire Engine Red (近似 RGB: 206, 32, 41).
|
||||
The objective, undeniable proof of their success. It's the central focus of the scene's opening and the single source of light, acting as a symbolic beacon of their achievement.
|
||||
* [PROP-03] A life-sized (approx. 6'5") statue of a stylized cartoon basketball player in a heroic pose—one arm bent, index finger pointing to the sky. It's painted in bright, glossy enamel colors. The base is a heavy black plinth with "SKY-HIGH SLAM" inscribed in a bold, energetic white font.
|
||||
The physical embodiment of their game and their long-held dream. It represents the "joke" that became a monumental success and is the origin of the partners' shared victory gesture. Handled by CH-02 Kai.
|
||||
5. **Core Atmosphere:**
|
||||
Primary Mood: Quiet Catharsis. The atmosphere is hushed, intimate, and thick with the weight of shared history and struggle. It is a moment of profound exhaustion mixed with the silent, deeply felt triumph of vindication. The mood is not one of loud celebration, but of a heavy burden being lifted, replaced by a shared, unspoken understanding.
|
||||
`;
|
||||
|
||||
const shot5 = `
|
||||
|
||||
==== Continuous Shots ====
|
||||
#### A. Director's Directive
|
||||
|
||||
Role: You are IMF, an expert cinematic video generation AI. I am the Film Director. Your task is to translate my detailed storyboard and production bible into a photorealistic, cinematic sequence. Adhere strictly to the shot descriptions, character details, and overall aesthetic.
|
||||
|
||||
#### B. Narrative Prompt for IMF
|
||||
|
||||
--- SCENE [S01] (Location: [SC-01: Game Studio]) ---
|
||||
* **Narrative Goal:** The scene's dramatic heart is the quiet, shared vindication of two contrasting partners, whose ridiculed passion project achieves ultimate success, culminating in a single, cathartic gesture that solidifies their bond and validates their struggle.
|
||||
* **Scene Environment Settings:**
|
||||
* **Time of Day:** Night, approx. 3 AM. The air is still and silent.
|
||||
* **Weather:** Clear. (Interior Scene)
|
||||
* **Lighting:** High-contrast and moody. The primary light source is the cool, blue-white glow from the \`PROP-02: Analytics Monitor\`, casting deep shadows across the cluttered office. A single, soft spotlight might fall near the central mascot statue, but the overall feeling is of darkness punctuated by a single point of victory.
|
||||
* **Character Blocking:** The scene begins with \`[CH-01: Wei]\` seated alone at his desk. When \`[CH-02: Kai]\` enters, he walks from the door (frame right) to stand beside Wei's desk. Later, they both move to a new blocking position, standing on either side of the \`[PROP-03: Mascot Statue]\` in the center of the room.
|
||||
|
||||
2. **Master Character List:**
|
||||
*[CH-01] Wei
|
||||
1. **Basic Information**:
|
||||
|
||||
- Gender: Male
|
||||
|
||||
- Age: 45
|
||||
|
||||
- Body Type: The person has an average build, with a slight protrusion of the abdomen, estimated to weigh 75 kg.
|
||||
|
||||
- Height Estimation: 175 cm
|
||||
|
||||
- Race: East Asian
|
||||
|
||||
2. **Facial Details**:
|
||||
|
||||
- Eyes: The eyes are of medium size, approximately 28 mm in length and 10 mm in width. They are almond-shaped with a slight upward slant at the outer corners. The palpebral fissures are wide, and the inner canthal angles are sharp, while the outer canthal angles are slightly tilted upward. The distance between the eyes is approximately 32 mm. The pupil color is dark brown, and the iris features are not distinguishable.
|
||||
|
||||
- Nose: The nose is approximately 50 mm long and 35 mm wide at the base. The nose bridge is low and broad. The root of the nose is wide, and the tip is slightly rounded. The nostrils are directed slightly forward. The angle of the nose tip is approximately 95 degrees. The nose bridge appears straight.
|
||||
|
||||
- Mouth: The mouth is approximately 50 mm wide, with a slight downturn at the corners. The lips are of medium thickness, with a defined vermilion border. The mouth is positioned centrally on the face. The lips are a light pink color with no clear lip line.
|
||||
|
||||
- Ears: The ears are of average size, approximately 65 mm in length and 35 mm in width. The auricle is oval-shaped, and the earlobe is detached. The top of the ear is positioned at the same height as the eyebrows, and the bottom is at the same height as the nose. The earlobe has a rounded outline.
|
||||
|
||||
- Face Shape: The face shape is rounded with a soft jawline and a full chin. The cheekbones are not prominent. The width-to-height ratio is approximately 1:1.3. The chin is short and rounded. The face is mostly symmetrical.
|
||||
|
||||
- Forehead: The forehead is high and wide, approximately 60 mm in height. The hairline is straight and slightly receding. The forehead is positioned high on the face, with a distance of approximately 35 mm between the forehead and eyebrows.
|
||||
|
||||
3. **Appearance**:
|
||||
|
||||
- Skin Color: The person's skin is a light beige color with a hint of a yellow undertone, consistent with Fitzpatrick scale type 3. The skin appears smooth with no visible blemishes. The skin on the face is a consistent color with no signs of redness or discoloration.
|
||||
|
||||
- Hair: The hair is black and short, styled in a crew cut, and does not cover any part of the face. The hair is neatly trimmed and appears to be slightly thinning at the crown. The temples are also showing some gray hairs, which are mixed in with the black hair.
|
||||
|
||||
- Facial Features: The person is wearing a pair of clear-framed, rectangular glasses. The eyebrows are black, short, and slightly thick, with a natural shape. There is no visible facial hair or makeup.
|
||||
|
||||
- Other Notable Features: The person has a yellow and blue bracelet on their left wrist, which appears to be made of cloth or rubber. There is a watch on the left wrist, but it is not clearly visible. The person is holding a folded, white cloth in their left hand.
|
||||
|
||||
4. **Clothing and Accessories**:
|
||||
|
||||
- Upper Body: The person is wearing a loose-fitting white T-shirt made of cotton. It has a small, faint, blue graphic on the left chest area. The T-shirt is short-sleeved and has no other visible patterns or logos.
|
||||
|
||||
- Bottoms: The person is wearing loose-fitting shorts that appear to be made of a terry cloth-like material. The shorts are a bright green color with a raised, textured, grid-like pattern. They are knee-length.
|
||||
|
||||
- Shoes: The shoes are not visible in the photo.
|
||||
|
||||
- Accessories: The person has a yellow and blue bracelet on their left wrist. A watch is visible on the same wrist, but its details are obscured. He is also wearing a pair of glasses.
|
||||
*[CH-02] Kai
|
||||
1. **Basic Information**:
|
||||
|
||||
- Gender: Male
|
||||
|
||||
- Age: 35
|
||||
|
||||
- Body Type: The person has a rounded, overweight build, weighing approximately 85 kg.
|
||||
|
||||
- Height Estimation: 170 cm
|
||||
|
||||
- Race: East Asian
|
||||
|
||||
2. **Facial Details**:
|
||||
|
||||
- Eyes: The eyes are narrow and slightly asymmetrical, with the right eye appearing slightly smaller than the left. The palpebral fissures are approximately 25 mm in length and 8 mm in width. The inner canthal angle is slightly upward, while the outer canthal angle is slightly downward. The distance between the eyes is approximately 30 mm. The pupil color is dark brown, and the iris is not clearly visible.
|
||||
|
||||
- Nose: The nose is approximately 45 mm in length and 40 mm in width at the base. The bridge is low and broad. The root of the nose is wide, the tip is rounded and bulbous, and the wings are broad. The nostrils are directed slightly forward. The angle of the nose tip is approximately 95 degrees. The nose bridge appears curved.
|
||||
|
||||
- Mouth: The mouth is approximately 55 mm wide, with a neutral expression. The lips are of medium thickness, with a defined cupid's bow and a slightly downturned mouth angle. The mouth is positioned centrally on the face. The lip color is a light pink, and the lip line is well-defined.
|
||||
|
||||
- Ears: The ears are of average size, approximately 60 mm in length and 35 mm in width. The auricle is oval-shaped, and the earlobe is detached. The top of the ear is positioned at the same height as the eyes, while the bottom is at the same height as the nose. The earlobe has a rounded outline.
|
||||
|
||||
- Face Shape: The face is round with a soft, undefined jawline. The cheekbones are not prominent. The width-to-height ratio is approximately 1:1.1. The chin is short and rounded. The face is mostly symmetrical.
|
||||
|
||||
- Forehead: The forehead is high and wide, approximately 70 mm in height. The hairline is rounded and slightly receding at the temples. The forehead is positioned high on the face, with a distance of approximately 40 mm between the forehead and eyebrows.
|
||||
|
||||
3. **Appearance**:
|
||||
|
||||
- Skin Color: The person's skin is a light beige color with a warm undertone. There are several dark moles on the lower legs, with one particularly noticeable on the left shin. The skin has a smooth texture with no visible blemishes. The skin on the face is of a uniform tone.
|
||||
|
||||
- Hair: The hair is black, curly, and styled in a short afro. The length of the hair is approximately 10 cm, extending out from the scalp. The hair is thick and voluminous, but does not cover any part of the face. The hairline is slightly receding at the temples and forehead.
|
||||
|
||||
- Facial Features: The person is not wearing glasses. The eyebrows are thick, black, and have a slight arch. There is a small amount of dark facial hair on the upper lip, appearing as a thin mustache, along with some stubble on the chin. There is no visible makeup.
|
||||
|
||||
- Other Notable Features: The person has a number of small, dark moles on their legs. There are no visible tattoos, piercings, or other jewelry.
|
||||
|
||||
4. **Clothing and Accessories**:
|
||||
|
||||
- Upper Body: The person is wearing a loose-fitting, short-sleeved t-shirt in a light pink color. The shirt is made of cotton and features a stylized white logo on the chest.
|
||||
|
||||
- Bottoms: The person is wearing black cargo shorts made of a synthetic material. The shorts are loose-fitting and have multiple pockets on the sides.
|
||||
|
||||
- Shoes: The person is wearing white and beige sneakers. The shoes are low-cut and have a thick, layered sole. They appear to be made of leather or a synthetic equivalent.
|
||||
|
||||
- Accessories: No accessories are visible besides a single, dark sock on the right foot that is peeking out from the top of the sneaker.
|
||||
--- Clip [E01-S01-C08] Data ---
|
||||
* **Shot Type:** Two-Shot
|
||||
* **Frame Description:** Kai's smile widens into a grin. He claps a hand firmly on Wei's shoulder. The camera angle is from the side, capturing both of them in profile, lit by the monitor.
|
||||
* **Key Action / Focus:** The first moment of physical contact and shared celebration.
|
||||
* **Shot Continuity & Action Timeline:** State Check: \`[CH-01: Wei]\` is seated. \`[CH-02: Kai]\` is standing beside him. | Timeline: [0s-1s] Kai's smile widens. [1s-2s] He claps his hand on Wei's shoulder.
|
||||
* **Atmosphere / Mood:** Exuberant relief, camaraderie.
|
||||
* **Cinematography Blueprint:**
|
||||
* **Composition:** A classic two-shot, showing their connection. Shallow depth of field.
|
||||
* **Camera Motion:** Static.
|
||||
* **Dialogue & Performance:**
|
||||
* **Line:** "They laughed at us, Wei. Remember that budget meeting? They said we were building a ghost."
|
||||
* **Language:** english
|
||||
* **Delivery:** A rush of triumphant memory, his voice gaining strength and volume.
|
||||
* **Speaker:** \`[CH-02: Kai]\`
|
||||
|
||||
* **Shot 1:** A static two-shot from the side, capturing both **Kai [CH-02]** and **Wei [CH-01]** in profile, lit by the monitor's glow. The background is soft due to a shallow depth of field. Kai's quiet awe breaks as his smile widens into a broad, exuberant grin. He claps a hand firmly on Wei's shoulder, a moment of camaraderie. His voice, filled with a rush of triumphant memory, gains strength and volume as he speaks.
|
||||
Kai [CH-02]: They laughed at us, Wei. Remember that budget meeting? They said we were building a ghost.
|
||||
|
||||
|
||||
#### C. Digital Production Bible
|
||||
|
||||
0. **Dialogue Language:** english
|
||||
|
||||
1. **Overall Project Style:**
|
||||
Geographic Setting & Ethnicity: Mainland China, primarily Han Chinese characters.
|
||||
Format: This entire video is strictly Flawless, borderless, full-frame 16:9 video presentation. The visual style emulates the high dynamic range and rich color technology of the Arri Alexa camera, but the composition is designed for the 16:9 broadcast standard, ensuring there are no black edges or letterboxing.
|
||||
Aesthetics: Modern urban drama, hyper-realistic, 4K. The primary visual approach is a cinematic shallow depth of field (浅景深), creating a moody, intimate atmosphere that isolates the characters from the surrounding chaos of their office, focusing on their internal emotional states.
|
||||
Technical Blueprint:
|
||||
Color Science: natural skin tones, true-to-life colors, ARRI REVEAL Color Science look, AWG4 color space
|
||||
Dynamic Range and Tonality: wide dynamic range, 17 stops of latitude, soft film-like highlight roll-off, LogC4 tonal curve
|
||||
Frame Rate: 24fps,营造经典的电影动态模糊效果 (24fps, to create classic cinematic motion blur)
|
||||
|
||||
4. **Master Scene List:**
|
||||
* [SC-01] SC-01: Game Studio
|
||||
A medium-sized, open-plan office space with a lived-in, chaotic-creative aesthetic. The architecture is modern, with exposed ceiling ductwork and polished concrete floors. However, every surface is covered: walls are a dense collage of concept art, technical flowcharts, and notes; desks are cluttered with multiple monitors, drawing tablets, personal trinkets, and an archeological array of empty coffee cups and energy drink cans. A massive whiteboard wall is entirely filled with layers of code and diagrams. The entire space is a shrine to the intense, exhausting process of creation.
|
||||
|
||||
4. **Master Props List:**
|
||||
* [PROP-02] A sleek, modern 27-inch 4K computer monitor with a thin bezel, set to a dark mode UI. It displays a real-time graph with a glowing electric blue line. The crucial text "GLOBAL DOWNLOADS - RANK #1" is displayed prominently, with the "#1" in a stark, celebratory Fire Engine Red (近似 RGB: 206, 32, 41).
|
||||
The objective, undeniable proof of their success. It's the central focus of the scene's opening and the single source of light, acting as a symbolic beacon of their achievement.
|
||||
* [PROP-03] A life-sized (approx. 6'5") statue of a stylized cartoon basketball player in a heroic pose—one arm bent, index finger pointing to the sky. It's painted in bright, glossy enamel colors. The base is a heavy black plinth with "SKY-HIGH SLAM" inscribed in a bold, energetic white font.
|
||||
The physical embodiment of their game and their long-held dream. It represents the "joke" that became a monumental success and is the origin of the partners' shared victory gesture. Handled by CH-02 Kai.
|
||||
5. **Core Atmosphere:**
|
||||
Primary Mood: Quiet Catharsis. The atmosphere is hushed, intimate, and thick with the weight of shared history and struggle. It is a moment of profound exhaustion mixed with the silent, deeply felt triumph of vindication. The mood is not one of loud celebration, but of a heavy burden being lifted, replaced by a shared, unspoken understanding.
|
||||
|
||||
|
||||
`;
|
||||
});
|
||||
});
|
||||
@ -2,11 +2,11 @@
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Mic, MicOff, Upload, Play, Pause, Trash2, X } from "lucide-react";
|
||||
import { Tooltip, Upload as AntdUpload } from "antd";
|
||||
import { Tooltip, Upload as AntdUpload, message } from "antd";
|
||||
import { InboxOutlined } from "@ant-design/icons";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
|
||||
import { useUploadFile } from "../../app/service/domain/service";
|
||||
import { getAudioDuration, useUploadFile } from "../../app/service/domain/service";
|
||||
|
||||
// 自定义样式
|
||||
const audioRecorderStyles = `
|
||||
@ -84,7 +84,6 @@ export function AudioRecorder({
|
||||
audioUrl,
|
||||
onAudioRecorded,
|
||||
onAudioDeleted,
|
||||
title = "请上传参考音频",
|
||||
showCloseButton = false,
|
||||
onClose,
|
||||
}: AudioRecorderProps) {
|
||||
@ -112,11 +111,20 @@ export function AudioRecorder({
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
mediaRecorder.onstop = async () => {
|
||||
const audioBlob = new Blob(chunksRef.current, { type: "audio/wav" });
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
onAudioRecorded(audioBlob, audioUrl);
|
||||
const duration = await getAudioDuration(audioBlob);
|
||||
|
||||
// 无论时长是否符合要求,都要释放麦克风权限
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
|
||||
if(duration>20||duration<10){
|
||||
message.warning("Please keep your audio between 10 and 20 seconds to help us better learn your voice.")
|
||||
return;
|
||||
}
|
||||
|
||||
onAudioRecorded(audioBlob, audioUrl);
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
@ -180,13 +188,23 @@ export function AudioRecorder({
|
||||
<Tooltip
|
||||
title="Please clearly read the story description above and record a 15-second audio file for upload"
|
||||
placement="top"
|
||||
overlayClassName="max-w-xs"
|
||||
classNames={{ root: "max-w-xs" }}
|
||||
>
|
||||
<div>
|
||||
<AntdUpload.Dragger
|
||||
accept="audio/*"
|
||||
beforeUpload={() => false}
|
||||
beforeUpload={async(file) => {
|
||||
console.log("beforeUpload 被调用:", file);
|
||||
// 移除 return false,让文件能够正常进入 customRequest
|
||||
const duration = await getAudioDuration(file);
|
||||
if(duration>20||duration<10){
|
||||
message.warning("Please keep your audio between 10 and 20 seconds to help us better learn your voice.")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
customRequest={async ({ file, onSuccess, onError }) => {
|
||||
console.log("customRequest 被调用,文件:", file);
|
||||
try {
|
||||
const fileObj = file as File;
|
||||
console.log(
|
||||
@ -219,6 +237,12 @@ export function AudioRecorder({
|
||||
showUploadList={false}
|
||||
className="bg-transparent border-dashed border-white/20 hover:border-white/40"
|
||||
disabled={isUploading}
|
||||
onChange={(info) => {
|
||||
console.log("Upload onChange 事件:", info);
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
console.log("文件拖拽事件:", e);
|
||||
}}
|
||||
>
|
||||
<div className="text-2xl text-white/40 mb-2">
|
||||
<InboxOutlined />
|
||||
@ -390,14 +414,36 @@ export function AudioRecorder({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频波形播放器组件的属性接口
|
||||
*/
|
||||
interface WaveformPlayerProps {
|
||||
/** 音频文件的URL地址 */
|
||||
audioUrl: string;
|
||||
/** 当前播放状态,true表示正在播放,false表示暂停或停止 */
|
||||
isPlaying: boolean;
|
||||
/** 播放状态变化时的回调函数,用于同步外部播放状态 */
|
||||
onPlayStateChange: (isPlaying: boolean) => void;
|
||||
/** 音量大小,范围0-1,默认为1(最大音量) */
|
||||
volume?: number;
|
||||
/** 是否静音,true表示静音,false表示有声音,默认为false */
|
||||
isMuted?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频波形播放器组件
|
||||
*
|
||||
* 使用 WaveSurfer.js 库创建可视化的音频波形播放器,支持播放控制、音量调节和静音功能。
|
||||
* 组件会自动同步外部传入的播放状态,确保播放控制的一致性。
|
||||
*
|
||||
* @param {string} audioUrl - 音频文件的URL地址
|
||||
* @param {boolean} isPlaying - 当前播放状态
|
||||
* @param {function} onPlayStateChange - 播放状态变化回调函数
|
||||
* @param {number} [volume=1] - 音量大小,范围0-1
|
||||
* @param {boolean} [isMuted=false] - 是否静音
|
||||
* @returns {JSX.Element} 渲染的波形播放器组件
|
||||
* ```
|
||||
*/
|
||||
function WaveformPlayer({
|
||||
audioUrl,
|
||||
isPlaying,
|
||||
@ -405,47 +451,61 @@ function WaveformPlayer({
|
||||
volume = 1,
|
||||
isMuted = false,
|
||||
}: WaveformPlayerProps) {
|
||||
/** 容器DOM元素的引用,用于挂载WaveSurfer实例 */
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
/** WaveSurfer实例的引用,用于控制音频播放 */
|
||||
const wavesurferRef = useRef<WaveSurfer | null>(null);
|
||||
|
||||
/**
|
||||
* 初始化WaveSurfer实例
|
||||
* 当audioUrl变化时重新创建实例
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || !audioUrl) return;
|
||||
|
||||
// 创建WaveSurfer实例并配置样式
|
||||
const ws = WaveSurfer.create({
|
||||
container: containerRef.current,
|
||||
waveColor: "#3b82f6",
|
||||
progressColor: "#1d4ed8",
|
||||
cursorColor: "#1e40af",
|
||||
height: 64,
|
||||
barWidth: 2,
|
||||
barGap: 1,
|
||||
normalize: true,
|
||||
url: audioUrl,
|
||||
waveColor: "#3b82f6", // 波形颜色
|
||||
progressColor: "#1d4ed8", // 播放进度颜色
|
||||
cursorColor: "#1e40af", // 播放光标颜色
|
||||
height: 64, // 波形高度
|
||||
barWidth: 2, // 波形条宽度
|
||||
barGap: 1, // 波形条间距
|
||||
normalize: true, // 自动标准化波形
|
||||
url: audioUrl, // 音频文件URL
|
||||
});
|
||||
|
||||
// 监听播放状态变化
|
||||
ws.on("play", () => onPlayStateChange(true));
|
||||
ws.on("pause", () => onPlayStateChange(false));
|
||||
ws.on("finish", () => onPlayStateChange(false));
|
||||
// 监听播放状态变化事件,同步到外部状态
|
||||
ws.on("play", () => onPlayStateChange(true)); // 开始播放
|
||||
ws.on("pause", () => onPlayStateChange(false)); // 暂停播放
|
||||
ws.on("finish", () => onPlayStateChange(false)); // 播放结束
|
||||
|
||||
wavesurferRef.current = ws;
|
||||
|
||||
// 组件卸载时销毁WaveSurfer实例,释放资源
|
||||
return () => {
|
||||
ws.destroy();
|
||||
};
|
||||
}, [audioUrl, onPlayStateChange]);
|
||||
|
||||
// 同步外部播放状态和音量
|
||||
/**
|
||||
* 同步外部播放状态和音量设置
|
||||
* 当isPlaying、volume或isMuted变化时,同步到WaveSurfer实例
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!wavesurferRef.current) return;
|
||||
|
||||
// 同步播放状态:如果外部状态为播放但WaveSurfer未播放,则开始播放
|
||||
if (isPlaying && !wavesurferRef.current.isPlaying()) {
|
||||
wavesurferRef.current.play();
|
||||
} else if (!isPlaying && wavesurferRef.current.isPlaying()) {
|
||||
}
|
||||
// 如果外部状态为暂停但WaveSurfer正在播放,则暂停播放
|
||||
else if (!isPlaying && wavesurferRef.current.isPlaying()) {
|
||||
wavesurferRef.current.pause();
|
||||
}
|
||||
|
||||
// 设置音量
|
||||
// 设置音量:静音时设为0,否则使用传入的音量值
|
||||
const currentVolume = isMuted ? 0 : volume;
|
||||
wavesurferRef.current.setVolume(currentVolume);
|
||||
}, [isPlaying, volume, isMuted]);
|
||||
|
||||
@ -53,6 +53,7 @@ const RenderTemplateStoryMode = ({
|
||||
setActiveRoleIndex,
|
||||
setActiveRoleImage,
|
||||
setActiveRoleAudio,
|
||||
clearData
|
||||
} = useTemplateStoryServiceHook();
|
||||
|
||||
// 本地加载状态,用于 UI 反馈
|
||||
@ -147,11 +148,7 @@ const RenderTemplateStoryMode = ({
|
||||
};
|
||||
// 故事编辑器渲染
|
||||
const storyEditorRender = () => {
|
||||
return isLoading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
|
||||
</div>
|
||||
) : selectedTemplate ? (
|
||||
return selectedTemplate ? (
|
||||
<div className="relative h-full">
|
||||
{/* 模板信息头部 - 增加顶部空间 */}
|
||||
<div className="flex gap-3 py-4 border-b border-white/[0.1] h-[300px]">
|
||||
@ -283,7 +280,11 @@ const RenderTemplateStoryMode = ({
|
||||
Characters
|
||||
</h4>
|
||||
{selectedTemplate.storyRole.map((role, index: number) => (
|
||||
<Tooltip key={index} title={role.role_name} placement="left">
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={role.role_name}
|
||||
placement="left"
|
||||
>
|
||||
<button
|
||||
data-alt={`character-thumbnail-${index}`}
|
||||
className={`w-full aspect-square rounded-lg overflow-hidden border-2 transition-all duration-200 hover:scale-105 ${
|
||||
@ -304,37 +305,9 @@ const RenderTemplateStoryMode = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 弹窗底部操作 - 只保留 Action 按钮 */}
|
||||
{/* <div className="relative group flex justify-end mt-10">
|
||||
<div className="relative w-40 h-12 opacity-90 overflow-hidden rounded-xl bg-black z-10">
|
||||
<div className="absolute z-10 -translate-x-32 group-hover:translate-x-[20rem] ease-in transistion-all duration-700 h-full w-32 bg-gradient-to-r from-gray-500 to-white/10 opacity-30 -skew-x-12"></div>
|
||||
|
||||
<div className="absolute flex items-center justify-center text-white z-[1] opacity-90 rounded-2xl inset-0.5 bg-black">
|
||||
<button
|
||||
name="text"
|
||||
className="input font-semibold text-base h-full opacity-90 w-full px-10 py-2 rounded-xl bg-black flex items-center justify-center"
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{localLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
Actioning...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clapperboard className="w-5 h-5" />
|
||||
Action
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="absolute duration-1000 group-hover:animate-spin w-full h-[80px] bg-gradient-to-r from-[rgb(106,244,249)] to-[rgb(199,59,255)] blur-[30px]"></div>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className=" absolute bottom-0 right-0">
|
||||
<ActionButton
|
||||
isCreating={localLoading>0}
|
||||
isCreating={localLoading > 0}
|
||||
handleCreateVideo={handleConfirm}
|
||||
icon={<Clapperboard className="w-5 h-5" />}
|
||||
/>
|
||||
@ -348,7 +321,7 @@ const RenderTemplateStoryMode = ({
|
||||
<p className="text-sm">Please try again later</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@ -356,14 +329,13 @@ const RenderTemplateStoryMode = ({
|
||||
open={isOpen}
|
||||
onCancel={() => {
|
||||
// 清空所有选中的内容数据
|
||||
setSelectedTemplate(null);
|
||||
setActiveRoleIndex(0);
|
||||
clearData()
|
||||
onClose();
|
||||
}}
|
||||
footer={null}
|
||||
width="60%"
|
||||
style={{ maxWidth: "800px", marginTop: "0vh" }}
|
||||
className="template-modal"
|
||||
className="photo-story-modal !pb-0 rounded-lg bg-white/[0.08] backdrop-blur-[20px] [&_.ant-modal-content]:bg-white/[0.00]"
|
||||
closeIcon={
|
||||
<div className="w-6 h-6 bg-white/10 rounded-full flex items-center justify-center hover:bg-white/20 transition-colors">
|
||||
<span className="text-white/70 text-lg leading-none flex items-center justify-center">
|
||||
@ -379,10 +351,12 @@ const RenderTemplateStoryMode = ({
|
||||
Template Story Selection
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
{templateListRender()}
|
||||
<div className="flex-1">{storyEditorRender()}</div>
|
||||
</div>
|
||||
<GlobalLoad show={isLoading} progress={0}>
|
||||
<div className="flex gap-4">
|
||||
{templateListRender()}
|
||||
<div className="flex-1">{storyEditorRender()}</div>
|
||||
</div>
|
||||
</GlobalLoad>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
@ -797,7 +771,9 @@ const PhotoStoryModal = ({
|
||||
// 处理图片上传
|
||||
const handleImageUpload = async (e: any) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
if (!(target.tagName == "IMG" || e.target.dataset.alt =="image-upload-area")) {
|
||||
if (
|
||||
!(target.tagName == "IMG" || e.target.dataset.alt == "image-upload-area")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
@ -839,7 +815,7 @@ const PhotoStoryModal = ({
|
||||
};
|
||||
|
||||
const handleAnalyzeImage = async () => {
|
||||
let timeout = 100
|
||||
let timeout = 100;
|
||||
let timer: NodeJS.Timeout;
|
||||
timer = setInterval(() => {
|
||||
setLocalLoading((prev) => {
|
||||
@ -851,8 +827,8 @@ const PhotoStoryModal = ({
|
||||
}, timeout);
|
||||
try {
|
||||
await uploadAndAnalyzeImage();
|
||||
} finally {
|
||||
timeout=10
|
||||
} finally {
|
||||
timeout = 10;
|
||||
clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
setLocalLoading((prev) => {
|
||||
@ -863,7 +839,6 @@ const PhotoStoryModal = ({
|
||||
return prev + 1;
|
||||
});
|
||||
}, timeout);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -883,7 +858,7 @@ const PhotoStoryModal = ({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<GlobalLoad show={localLoading>0} progress={localLoading}>
|
||||
<GlobalLoad show={localLoading > 0} progress={localLoading}>
|
||||
<div className="rounded-2xl">
|
||||
{/* 弹窗头部 */}
|
||||
<div className="flex items-center gap-3 p-2 border-b border-white/[0.1]">
|
||||
|
||||
@ -50,7 +50,7 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
|
||||
<div className="floating-circle floating-circle-2"></div>
|
||||
<div className="floating-circle floating-circle-3"></div>
|
||||
<div className="front-content-overlay">
|
||||
<div className="free-badge">Free</div>
|
||||
<div className="free-badge">boutique</div>
|
||||
<div className="text-content">
|
||||
<h3 className="card-title">{title}</h3>
|
||||
<p className="card-description">{description}</p>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import { checkAuth, isAuthenticated } from '@/lib/auth';
|
||||
import GlobalLoad from '../common/GlobalLoad';
|
||||
|
||||
interface AuthGuardProps {
|
||||
children: React.ReactNode;
|
||||
@ -36,7 +37,7 @@ export default function AuthGuard({ children }: AuthGuardProps) {
|
||||
try {
|
||||
// 调用鉴权接口验证token
|
||||
const isValid = await checkAuth();
|
||||
|
||||
|
||||
if (isValid) {
|
||||
setIsAuthorized(true);
|
||||
} else {
|
||||
@ -58,7 +59,7 @@ export default function AuthGuard({ children }: AuthGuardProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<GlobalLoad show={true} progress={0} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -70,4 +71,4 @@ export default function AuthGuard({ children }: AuthGuardProps) {
|
||||
|
||||
// 授权通过,渲染子组件
|
||||
return <>{children}</>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ interface GlobalLoadProps {
|
||||
/** 是否显示加载状态 */
|
||||
show: boolean;
|
||||
/** 子元素内容 */
|
||||
children: ReactNode;
|
||||
children?: ReactNode;
|
||||
/** 进度条数值 0-100,非必须 */
|
||||
progress?: number;
|
||||
/** 旋转圆环直径,非必须 */
|
||||
@ -23,7 +23,7 @@ interface GlobalLoadProps {
|
||||
*/
|
||||
export default function GlobalLoad({
|
||||
show,
|
||||
children,
|
||||
children=<></>,
|
||||
progress,
|
||||
spinnerDiameter,
|
||||
progressWidth,
|
||||
@ -40,7 +40,7 @@ export default function GlobalLoad({
|
||||
className="!w-max !h-max !flex flex-col items-center justify-center gap-4 -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
{showSpinner && <TailwindSpinner diameter={spinnerDiameter} />}
|
||||
{progress && <TailwindLinearLoader progress={progress} width={progressWidth} />}
|
||||
{Boolean(progress) && <TailwindLinearLoader progress={progress as number} width={progressWidth} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user