2025-08-13 22:14:19 +08:00

510 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback, useMemo } from "react";
import { RoleEntity, AITextEntity } from "../domain/Entities";
import { RoleEditUseCase } from "../usecase/RoleEditUseCase";
import { getUploadToken, uploadToQiniu } from "@/api/common";
import {
analyzeImageDescription,
checkShotVideoStatus,
} from "@/api/video_flow";
import { SaveEditUseCase } from "../usecase/SaveEditUseCase";
/**
* 角色服务Hook返回值接口
*/
interface UseRoleService {
// 响应式数据
/** 角色列表 */
roleList: RoleEntity[];
/** 当前选中的角色 */
selectedRole: RoleEntity | null;
/** 当前角色的AI文本 */
currentRoleText: string | null;
/** 角色图片URL */
roleImageUrl: string;
/** 用户角色库 */
userRoleLibrary: RoleEntity[];
// 操作方法
/** 获取角色列表 */
fetchRoleList: (projectId: string) => Promise<void>;
/** 选择角色 */
selectRole: (role: RoleEntity) => Promise<void>;
/** 优化AI文本 */
optimizeRoleText: (userSuggestion: string) => Promise<void>;
/** 修改AI文本 */
updateRoleText: (newContent: string) => Promise<void>;
/** 重新生成角色 */
regenerateRole: () => Promise<void>;
/** 获取用户角色库 */
fetchUserRoleLibrary: () => Promise<void>;
/** 替换角色 */
replaceRoleWithLibrary: (replaceRoleId: string) => Promise<void>;
/** 上传图片并更新角色信息 */
uploadImageAndUpdateRole: (file: File) => Promise<void>;
/** 保存重新生成的角色到角色库 */
saveRoleToLibrary: () => Promise<void>;
/** 保存数据 */
saveData: () => Promise<void>;
}
/**
* 角色服务Hook
* 提供角色相关的所有响应式功能和业务逻辑
*/
export const useRoleServiceHook = (): UseRoleService => {
// 响应式状态
const [roleList, setRoleList] = useState<RoleEntity[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleEntity | null>(null);
const [currentRoleText, setCurrentRoleText] = useState<string | null>(null);
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
const [projectId, setProjectId] = useState<string>(""); // 添加项目ID状态
const [cacheRole, setCacheRole] = useState<RoleEntity | null>(null);
// UseCase实例 - 在角色选择时初始化
const [roleEditUseCase, setRoleEditUseCase] =
useState<RoleEditUseCase | null>(null);
// 计算属性
/**
* 角色图片URL
* @description 获取当前选中角色的图片URL
*/
const roleImageUrl = useMemo(() => {
return selectedRole?.imageUrl || "";
}, [selectedRole]);
/**
* 获取角色列表
* @description 根据项目ID获取所有角色列表
* @param projectId 项目ID
* @throws {Error} 当API调用失败时抛出错误
* @returns {Promise<void>} 获取完成后的Promise
*/
const fetchRoleList = useCallback(async (projectId: string) => {
try {
// 保存项目ID到状态
setProjectId(projectId);
// 初始化角色编辑UseCase实例
const newRoleEditUseCase = new RoleEditUseCase();
const roleList = await newRoleEditUseCase.getRoleList(projectId);
setRoleList(roleList);
setRoleEditUseCase(newRoleEditUseCase);
} catch (error) {
console.error("获取角色列表失败:", error);
throw error;
}
}, []);
/**
* 选择角色
* @description 根据角色ID选择角色并初始化相关的UseCase实例
* @param roleId 角色ID
*/
const selectRole = useCallback(
async (role: RoleEntity) => {
console.log("selectRole", role);
// 根据 role.name 完全替换掉旧的数据
setRoleList((prev) => prev.map((r) => (r.name === role.name ? role : r)));
setSelectedRole(role);
// 如果缓存角色为空,则设置缓存角色,名字不同也切换
if (!cacheRole || cacheRole.name !== role.name) {
setCacheRole(role);
}
// 调用selectRole方法
roleEditUseCase!.selectRole(role);
},
[roleEditUseCase]
);
/**
* 优化AI文本
* @description 对当前角色的AI文本进行优化
* @throws {Error} 当没有可优化的文本内容或UseCase未初始化时抛出错误
* @returns {Promise<void>} 优化完成后的Promise
*/
const optimizeRoleText = useCallback(async () => {
if (!roleEditUseCase) {
throw new Error("角色编辑UseCase未初始化");
}
if (!currentRoleText) {
throw new Error("没有可优化的文本内容");
}
if (!selectedRole) {
throw new Error("没有选中的角色");
}
try {
const { optimizedDescription, keywords } =
await roleEditUseCase.optimizeRoleDescription(selectedRole);
setCurrentRoleText(optimizedDescription);
// 更新角色列表中的对应角色描述和标签
setRoleList((prev) =>
prev.map((role) =>
role.id === selectedRole?.id
? {
...role,
generateText: optimizedDescription,
tags: keywords.map((keyword) => ({
id: `tag_${Date.now()}_${Math.random()
.toString(36)
.substr(2, 9)}`,
/** 名称 */
name: keyword,
/** 内容 */
content: keyword,
loadingProgress: 100,
disableEdit: false,
updatedAt: Date.now(),
})),
}
: role
)
);
// 更新当前选中角色
if (selectedRole) {
selectRole({
...selectedRole,
generateText: optimizedDescription,
tags: keywords.map((keyword) => ({
id: `tag_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
/** 名称 */
name: keyword,
/** 内容 */
content: keyword,
loadingProgress: 100,
disableEdit: false,
updatedAt: Date.now(),
})),
});
}
} catch (error) {
console.error("优化角色文本失败:", error);
throw error;
}
}, [roleEditUseCase, currentRoleText, selectedRole, selectRole]);
/**
* 修改AI文本
* @description 手动修改当前角色的AI文本内容
* @param newContent 新的文本内容
* @throws {Error} 当没有可编辑的文本或UseCase未初始化时抛出错误
* @returns {Promise<void>} 修改完成后的Promise
*/
const updateRoleText = useCallback(
async (newContent: string) => {
if (!roleEditUseCase) {
throw new Error("角色编辑UseCase未初始化");
}
setCurrentRoleText(newContent);
// 更新角色列表中的对应角色描述
setRoleList((prev) =>
prev.map((role) =>
role.id === selectedRole?.id
? { ...role, generateText: newContent }
: role
)
);
// 更新当前选中角色
if (selectedRole) {
selectRole({ ...selectedRole, generateText: newContent });
}
},
[roleEditUseCase, selectRole, selectedRole]
);
/**
* 重新生成角色
* @description 使用AI文本重新生成角色
* @throws {Error} 当缺少重新生成角色所需的数据或UseCase未初始化时抛出错误
* @returns {Promise<void>} 重新生成完成后的Promise
*/
const regenerateRole = useCallback(async () => {
if (!roleEditUseCase) {
throw new Error("角色编辑UseCase未初始化");
}
if (!selectedRole || !currentRoleText) {
throw new Error("缺少重新生成角色所需的数据");
}
if (!projectId) {
throw new Error("缺少项目ID无法重新生成角色");
}
try {
const newRoleEntity = await roleEditUseCase.AIgenerateRole(
projectId,
selectedRole.name,
currentRoleText
);
selectRole(newRoleEntity);
// 更新角色列表
setRoleList((prev) =>
prev.map((role) =>
role.id === newRoleEntity.id ? newRoleEntity : role
)
);
} catch (error) {
console.error("重新生成角色失败:", error);
throw error;
}
}, [roleEditUseCase, selectedRole, currentRoleText, projectId, selectRole]);
/**
* 获取用户角色库
* @description 获取当前用户的角色库列表
* @throws {Error} 当API调用失败时抛出错误
* @returns {Promise<void>} 获取完成后的Promise
*/
const fetchUserRoleLibrary = useCallback(async () => {
try {
let useCase = roleEditUseCase;
// 如果没有初始化RoleEditUseCase创建一个新的实例
if (!useCase) {
useCase = new RoleEditUseCase();
setRoleEditUseCase(useCase);
}
// 从localStorage获取当前用户信息
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
if (!User.id) {
throw new Error("无法获取用户ID请重新登录");
}
// 获取当前选中角色的描述
const userDescription = selectedRole?.generateText || "";
const roleLibraryList = await useCase!.getRoleLibraryList(
User.id,
userDescription
);
setUserRoleLibrary(roleLibraryList);
} catch (error) {
console.error("获取用户角色库失败:", error);
throw error;
}
}, [roleEditUseCase, selectedRole]);
/**
* 替换角色
* @description 使用角色库中的角色替换当前角色,并重新获取角色详细数据
* @param replaceRoleId 替换的角色ID
* @throws {Error} 当未选择角色、API调用失败或UseCase未初始化时抛出错误
* @returns {Promise<void>} 替换完成后的Promise
*/
const replaceRoleWithLibrary = useCallback(
async (replaceRoleId: string) => {
if (!selectedRole) {
throw new Error("请先选择角色");
}
if (!roleEditUseCase) {
throw new Error("角色编辑UseCase未初始化");
}
try {
await roleEditUseCase.replaceRoleById(selectedRole.id, replaceRoleId);
// 更新角色列表
const libraryRole = userRoleLibrary.find(
(role) => role.id === replaceRoleId
);
if (libraryRole) {
const updatedRole = {
...selectedRole,
name: libraryRole.name,
generateText: libraryRole.generateText,
imageUrl: libraryRole.imageUrl,
fromDraft: false,
};
selectRole(updatedRole);
setRoleList((prev) =>
prev.map((role) =>
role.id === selectedRole.id ? updatedRole : role
)
);
}
} catch (error) {
console.error("替换角色失败:", error);
throw error;
}
},
[selectedRole, roleEditUseCase, userRoleLibrary, selectRole]
);
/**
* 上传图片并更新角色信息
* @description 上传图片文件到七牛云存储然后调用AI接口分析图片生成描述和高亮标签最后更新当前选中角色的图片、描述和标签
* @param file 要上传的文件
* @returns {Promise<void>} 上传和分析完成后的Promise
*/
const uploadImageAndUpdateRole = useCallback(
async (file: File) => {
if (!selectedRole) {
throw new Error("请先选择要更新的角色");
}
try {
// 1. 上传图片到七牛云
const { token } = await getUploadToken();
const imageUrl = await uploadToQiniu(file, token);
// 2. 调用图片分析接口获取描述
const result = await analyzeImageDescription({
image_url: imageUrl,
});
if (!result.successful) {
throw new Error(`图片分析失败: ${result.message}`);
}
const { description, highlights } = result.data;
// 3. 更新当前选中角色的图片、描述和标签
const updatedRole = {
...selectedRole,
imageUrl: imageUrl,
generateText: description,
tags: highlights.map((highlight: string, index: number) => ({
id: `tag_${Date.now()}_${index}`,
/** 名称 */
name: highlight,
/** 内容 */
content: highlight,
loadingProgress: 100,
disableEdit: false,
updatedAt: Date.now(),
})),
};
// 更新选中的角色
selectRole(updatedRole);
// 更新角色列表中的对应角色
setRoleList((prev) =>
prev.map((role) => (role.id === selectedRole.id ? updatedRole : role))
);
console.log("角色图片和描述更新成功:", updatedRole);
} catch (error) {
console.error("上传图片并分析失败:", error);
throw error;
}
},
[selectedRole, roleEditUseCase, selectRole]
);
/**
* 保存重新生成的角色到角色库
* @description 将当前选中的角色保存到角色库中
* @throws {Error} 当没有选中角色、UseCase未初始化或缺少项目ID时抛出错误
* @returns {Promise<void>} 保存完成后的Promise
*/
const saveRoleToLibrary = useCallback(async () => {
if (!selectedRole) {
throw new Error("请先选择要保存的角色");
}
if (!roleEditUseCase) {
throw new Error("角色编辑UseCase未初始化");
}
if (!projectId) {
throw new Error("缺少项目ID无法保存角色到角色库");
}
try {
// 从localStorage获取当前用户信息
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
if (!User.id) {
throw new Error("无法获取用户ID请重新登录");
}
// 调用保存重新生成角色到角色库的方法
const result = await roleEditUseCase.saveRegeneratedCharacterToLibrary(
selectedRole,
projectId,
String(User.id)
);
selectRole({
...selectedRole,
id: result.character_id,
fromDraft: true,
});
SaveEditUseCase.setCharacterId([
...SaveEditUseCase.characterId,
{
character_id: result.character_id,
name: selectedRole.name,
},
]);
console.log("角色保存到角色库成功:", result);
} catch (error) {
console.error("保存角色到角色库失败:", error);
throw error;
}
}, [selectedRole, roleEditUseCase, projectId]);
/**
* 保存数据
* @description 调用接口保存当前项目数据
* @throws {Error} 当缺少项目ID或API调用失败时抛出错误
* @returns {Promise<void>} 保存完成后的Promise
*/
const saveData = useCallback(async () => {
if (!projectId) {
throw new Error("缺少项目ID无法保存数据");
}
try {
const result = await checkShotVideoStatus({
project_id: projectId,
});
if (!result.successful) {
throw new Error(`保存数据失败: ${result.message}`);
}
console.log("数据保存成功");
} catch (error) {
console.error("保存数据失败:", error);
throw error;
}
}, [projectId]);
return {
// 响应式数据
roleList,
selectedRole,
currentRoleText,
roleImageUrl,
userRoleLibrary,
// 操作方法
fetchRoleList,
selectRole,
optimizeRoleText,
updateRoleText,
regenerateRole,
fetchUserRoleLibrary,
replaceRoleWithLibrary,
uploadImageAndUpdateRole,
saveRoleToLibrary,
saveData,
};
};