diff --git a/api/video_flow.ts b/api/video_flow.ts index d6980b6..1b71fa7 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -17,6 +17,7 @@ import { import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./allMovieType"; import { RoleResponse } from "@/app/service/usecase/RoleEditUseCase"; +import { RoleRecognitionResponse } from "@/app/service/usecase/ShotEditUsecase"; // API 响应类型 interface BaseApiResponse { @@ -820,7 +821,7 @@ export const updateShotPrompt = async (request: { /** * 人脸识别接口 * @param request - 人脸识别请求参数 - * @returns Promise> 人脸识别结果 + * @returns Promise> 人脸识别结果 */ export const faceRecognition = async (request: { /** 项目ID */ @@ -829,8 +830,8 @@ export const faceRecognition = async (request: { video_id: string; /** 目标图片URL */ target_image_url: string; -}): Promise> => { - return post>("/character/face_recognition", request); +}): Promise> => { + return post("/character/face_recognition", request); }; /** diff --git a/app/service/Interaction/SceneService.ts b/app/service/Interaction/SceneService.ts index 6e3719d..e69de29 100644 --- a/app/service/Interaction/SceneService.ts +++ b/app/service/Interaction/SceneService.ts @@ -1,427 +0,0 @@ -import { useState, useCallback, useMemo } from 'react'; -import { SceneEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities'; -import { SceneItem, TagItem, TextItem } from '../domain/Item'; -import { SceneEditUseCase } from '../usecase/SceneEditUseCase'; -import { TagEditUseCase } from '../usecase/TagEditUseCase'; -import { getSceneShots, getSceneData, getSceneList } from '@/api/video_flow'; - -/** - * 分镜选择项接口 - */ -interface ShotSelectionItem { - /** 分镜ID */ - id: string; - /** 分镜名称 */ - name: string; - /** 是否已选中 */ - selected: boolean; - /** 是否已应用场景 */ - applied: boolean; - /** 分镜数据 */ - shot: VideoSegmentEntity; -} - -/** - * 场景服务Hook返回值接口 - */ -interface UseSceneService { - // 响应式数据 - /** 场景列表 */ - sceneList: SceneItem[]; - /** 当前选中的场景 */ - selectedScene: SceneItem | null; - // /** 当前场景的AI文本 */ - // currentSceneText: TextItem | null; // TODO: TextEditUseCase not implemented yet - /** 当前场景的标签列表 */ - currentSceneTags: TagItem[]; - /** 场景图片URL */ - sceneImageUrl: string; - /** 分镜选择列表 */ - shotSelectionList: ShotSelectionItem[]; - /** 是否全选分镜 */ - isAllShotsSelected: boolean; - /** 已选中的分镜数量 */ - selectedShotsCount: number; - - // 操作方法 - /** 获取场景列表 */ - fetchSceneList: (projectId: string) => Promise; - /** 选择场景 */ - selectScene: (sceneId: string) => void; - /** 初始化当前选中场景的AI文本和标签数据 */ - initializeSceneData: () => Promise; - // /** 优化AI文本 */ - // optimizeSceneText: () => Promise; // TODO: TextEditUseCase not implemented yet - // /** 修改AI文本 */ - // updateSceneText: (newContent: string) => Promise; // TODO: TextEditUseCase not implemented yet - /** 修改标签内容 */ - updateTagContent: (tagId: string, newContent: string | number) => Promise; - /** 重新生成场景 */ - regenerateScene: () => Promise; - /** 获取场景出现的分镜列表 */ - fetchSceneShots: () => Promise; - /** 切换全选与全不选 */ - toggleSelectAllShots: () => void; - /** 选择/取消选择单个分镜 */ - toggleShotSelection: (shotId: string) => void; - /** 应用场景到选中的分镜 */ - applySceneToSelectedShots: () => Promise; -} - -/** - * 场景服务Hook - * 提供场景相关的所有响应式功能和业务逻辑 - */ -export const useSceneServiceHook = (): UseSceneService => { - // 响应式状态 - const [sceneList, setSceneList] = useState([]); - const [selectedScene, setSelectedScene] = useState(null); - // const [currentSceneText, setCurrentSceneText] = useState(null); // TODO: TextEditUseCase not implemented yet - const [currentSceneTags, setCurrentSceneTags] = useState([]); - const [shotSelectionList, setShotSelectionList] = useState([]); - - // UseCase实例 - 在场景选择时初始化 - const [sceneEditUseCase, setSceneEditUseCase] = useState(null); - // const [textEditUseCase, setTextEditUseCase] = useState(null); // TODO: TextEditUseCase not implemented yet - const [tagEditUseCases, setTagEditUseCases] = useState>(new Map()); - - // 计算属性 - /** - * 场景图片URL - * @description 获取当前选中场景的图片URL - */ - const sceneImageUrl = useMemo(() => { - return selectedScene?.entity.imageUrl || ''; - }, [selectedScene]); - - /** - * 是否全选分镜 - * @description 判断是否所有分镜都被选中 - */ - const isAllShotsSelected = useMemo(() => { - return shotSelectionList.length > 0 && shotSelectionList.every(shot => shot.selected); - }, [shotSelectionList]); - - /** - * 已选中的分镜数量 - * @description 获取当前选中的分镜数量 - */ - const selectedShotsCount = useMemo(() => { - return shotSelectionList.filter(shot => shot.selected).length; - }, [shotSelectionList]); - - /** - * 获取场景列表 - * @description 根据项目ID获取所有场景列表 - * @param projectId 项目ID - * @throws {Error} 当API调用失败时抛出错误 - * @returns {Promise} 获取完成后的Promise - */ - const fetchSceneList = useCallback(async (projectId: string) => { - try { - const response = await getSceneList({ - projectId: projectId - }); - - if (response.successful) { - const sceneItems = response.data.map(scene => new SceneItem(scene)); - setSceneList(sceneItems); - } else { - throw new Error(`获取场景列表失败: ${response.message}`); - } - } catch (error) { - console.error('获取场景列表失败:', error); - throw error; - } - }, []); - - /** - * 选择场景 - * @description 根据场景ID选择场景,并初始化相关的UseCase实例 - * @param sceneId 场景ID - */ - const selectScene = useCallback(async (sceneId: string) => { - const scene = sceneList.find(s => s.entity.id === sceneId); - if (scene) { - setSelectedScene(scene); - - // 初始化场景编辑UseCase实例 - setSceneEditUseCase(new SceneEditUseCase(scene)); - - // 清空文本和标签相关状态 - setTextEditUseCase(null); - setTagEditUseCases(new Map()); - setCurrentSceneText(null); - setCurrentSceneTags([]); - await initializeSceneData(); - } - }, [sceneList]); - - /** - * 初始化场景数据 - * @description 初始化当前选中场景的AI文本和标签数据 - * @throws {Error} 当未选择场景或API调用失败时抛出错误 - * @returns {Promise} 初始化完成后的Promise - */ - const initializeSceneData = useCallback(async () => { - if (!selectedScene) { - throw new Error('请先选择场景'); - } - - try { - const response = await getSceneData({ - sceneId: selectedScene.entity.id - }); - - if (response.successful) { - const { text, tags } = response.data; - const textItem = new TextItem(text); - const tagItems = tags.map(tag => new TagItem(tag)); - - // 设置当前场景的AI文本和标签 - setCurrentSceneText(textItem); - setCurrentSceneTags(tagItems); - - // 初始化文本UseCase - setTextEditUseCase(new TextEditUseCase(textItem)); - - // 初始化标签UseCase - const newTagEditUseCases = new Map(); - tagItems.forEach(tag => { - newTagEditUseCases.set(tag.entity.id, new TagEditUseCase(tag)); - }); - setTagEditUseCases(newTagEditUseCases); - } else { - throw new Error(`获取场景数据失败: ${response.message}`); - } - } catch (error) { - console.error('初始化场景数据失败:', error); - throw error; - } - }, [selectedScene]); - - /** - * 优化AI文本 - * @description 对当前场景的AI文本进行优化,无文本时不可进行优化 - * @throws {Error} 当没有可优化的文本内容或UseCase未初始化时抛出错误 - * @returns {Promise} 优化完成后的Promise - */ - const optimizeSceneText = useCallback(async () => { - if (!textEditUseCase) { - throw new Error('文本编辑UseCase未初始化'); - } - - if (!currentSceneText || !currentSceneText.entity.content) { - throw new Error('没有可优化的文本内容'); - } - - const optimizedContent = await textEditUseCase.getOptimizedContent(); - const updatedTextItem = await textEditUseCase.updateText(optimizedContent); - setCurrentSceneText(updatedTextItem); - }, [textEditUseCase, currentSceneText]); - - /** - * 修改AI文本 - * @description 手动修改当前场景的AI文本内容 - * @param newContent 新的文本内容 - * @throws {Error} 当没有可编辑的文本或UseCase未初始化时抛出错误 - * @returns {Promise} 修改完成后的Promise - */ - const updateSceneText = useCallback(async (newContent: string) => { - if (!textEditUseCase) { - throw new Error('文本编辑UseCase未初始化'); - } - - if (!currentSceneText) { - throw new Error('没有可编辑的文本'); - } - - const updatedTextItem = await textEditUseCase.updateText(newContent); - setCurrentSceneText(updatedTextItem); - }, [textEditUseCase, currentSceneText]); - - /** - * 修改标签内容 - * @description 修改指定标签的内容 - * @param tagId 标签ID - * @param newContent 新的标签内容 - * @throws {Error} 当标签不存在或UseCase未初始化时抛出错误 - * @returns {Promise} 修改完成后的Promise - */ - const updateTagContent = useCallback(async (tagId: string, newContent: string | number) => { - const tagEditUseCase = tagEditUseCases.get(tagId); - if (!tagEditUseCase) { - throw new Error(`标签编辑UseCase未初始化,标签ID: ${tagId}`); - } - - const updatedTagItem = await tagEditUseCase.updateTag(newContent); - - setCurrentSceneTags(prev => - prev.map(tag => - tag.entity.id === tagId - ? updatedTagItem - : tag - ) - ); - }, [tagEditUseCases]); - - /** - * 重新生成场景 - * @description 使用AI文本和标签重新生成场景 - * @throws {Error} 当缺少重新生成场景所需的数据或UseCase未初始化时抛出错误 - * @returns {Promise} 重新生成完成后的Promise - */ - const regenerateScene = useCallback(async () => { - if (!sceneEditUseCase) { - throw new Error('场景编辑UseCase未初始化'); - } - - if (!selectedScene || !currentSceneText || currentSceneTags.length === 0) { - throw new Error('缺少重新生成场景所需的数据'); - } - - const newSceneEntity = await sceneEditUseCase.AIgenerateScene(currentSceneText, currentSceneTags); - - const newSceneItem = new SceneItem(newSceneEntity); - setSelectedScene(newSceneItem); - - setSceneList(prev => - prev.map(scene => - scene.entity.id === newSceneEntity.id ? newSceneItem : scene - ) - ); - }, [sceneEditUseCase, selectedScene, currentSceneText, currentSceneTags]); - - /** - * 获取场景出现的分镜列表 - * @description 获取当前场景应用到的分镜列表,包括已应用状态 - * @throws {Error} 当未选择场景或API调用失败时抛出错误 - * @returns {Promise} 获取完成后的Promise - */ - const fetchSceneShots = useCallback(async () => { - if (!selectedScene) { - throw new Error('请先选择场景'); - } - - try { - const response = await getSceneShots({ - sceneId: selectedScene.entity.id - }); - - if (response.successful) { - const { shots, appliedShotIds } = response.data; - - const shotSelectionItems: ShotSelectionItem[] = shots.map(shot => ({ - id: shot.id, - name: shot.name, - selected: false, - applied: appliedShotIds.includes(shot.id), - shot - })); - - setShotSelectionList(shotSelectionItems); - } else { - throw new Error(`获取场景分镜列表失败: ${response.message}`); - } - } catch (error) { - console.error('获取场景分镜列表失败:', error); - throw error; - } - }, [selectedScene]); - - /** - * 切换全选与全不选 - * @description 如果当前是全选状态则全不选,否则全选 - */ - const toggleSelectAllShots = useCallback(() => { - setShotSelectionList(prev => { - const isAllSelected = prev.length > 0 && prev.every(shot => shot.selected); - return prev.map(shot => ({ ...shot, selected: !isAllSelected })); - }); - }, []); - - /** - * 选择/取消选择单个分镜 - * @description 切换指定分镜的选择状态 - * @param shotId 分镜ID - */ - const toggleShotSelection = useCallback((shotId: string) => { - setShotSelectionList(prev => - prev.map(shot => - shot.id === shotId - ? { ...shot, selected: !shot.selected } - : shot - ) - ); - }, []); - - /** - * 应用场景到选中的分镜 - * @description 将当前场景应用到选中的分镜,并更新应用状态 - * @throws {Error} 当未选择场景、未选择分镜或UseCase未初始化时抛出错误 - * @returns {Promise} 应用完成后的Promise - */ - const applySceneToSelectedShots = useCallback(async () => { - if (!sceneEditUseCase) { - throw new Error('场景编辑UseCase未初始化'); - } - - if (!selectedScene) { - throw new Error('请先选择场景'); - } - - const selectedShotIds = shotSelectionList - .filter(shot => shot.selected) - .map(shot => shot.id); - - if (selectedShotIds.length === 0) { - throw new Error('请先选择要应用的分镜'); - } - - await sceneEditUseCase.applyScene(selectedShotIds); - - setShotSelectionList(prev => - prev.map(shot => - selectedShotIds.includes(shot.id) - ? { ...shot, applied: true, selected: false } - : shot - ) - ); - }, [sceneEditUseCase, selectedScene, shotSelectionList]); - - return { - // 响应式数据 - sceneList, - selectedScene, - currentSceneText, - currentSceneTags, - sceneImageUrl, - shotSelectionList, - isAllShotsSelected, - selectedShotsCount, - - // 操作方法 - /** 获取场景列表 */ - fetchSceneList, - /** 选择场景 */ - selectScene, - /** 初始化当前选中场景的AI文本和标签数据 */ - initializeSceneData, - /** 优化AI文本 */ - optimizeSceneText, - /** 修改AI文本 */ - updateSceneText, - /** 修改标签内容 */ - updateTagContent, - /** 重新生成场景 */ - regenerateScene, - /** 获取场景出现的分镜列表 */ - fetchSceneShots, - /** 切换全选与全不选 */ - toggleSelectAllShots, - /** 选择/取消选择单个分镜 */ - toggleShotSelection, - /** 应用场景到选中的分镜 */ - applySceneToSelectedShots - }; -}; diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index a1d95f4..d537b1c 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -40,7 +40,7 @@ export interface UseShotService { /** 删除指定镜头 */ deleteLens: (lensName: string) => void; /** 获取视频当前帧并上传到七牛云 */ - filterRole: (video: HTMLVideoElement, projectId: string, videoId: string) => Promise; + filterRole: (video: HTMLVideoElement) => Promise; /** 设置角色简单数据 */ setSimpleCharacter: (characters: SimpleCharacter[]) => void; } @@ -75,8 +75,6 @@ export const useShotService = (): UseShotService => { const segments = await vidoEditUseCase.getVideoSegmentList(projectId); setProjectId(projectId); - console.log('segments', segments); - setVideoSegments(segments); setIntervalIdHandler(); } catch (error) { @@ -97,6 +95,9 @@ export const useShotService = (): UseShotService => { // 定义定时任务,每5秒执行一次 const newIntervalId = setInterval(async () => { try { + if (!projectId) { + return; + } const segments = await vidoEditUseCase.getVideoSegmentList(projectId); setVideoSegments(prevSegments => { @@ -345,7 +346,7 @@ export const useShotService = (): UseShotService => { // 上传到七牛云 const imageUrl = await uploadToQiniu(file, token); - + console.log('imageUrl', imageUrl); // 调用用例中的识别角色方法 if (vidoEditUseCase) { try { diff --git a/app/service/domain/Item.ts b/app/service/domain/Item.ts index 84abd70..97c9037 100644 --- a/app/service/domain/Item.ts +++ b/app/service/domain/Item.ts @@ -70,34 +70,6 @@ export class TextItem extends EditItem { } } -/** - * 角色可编辑项 - */ -export class RoleItem extends EditItem { - type: ItemType.IMAGE = ItemType.IMAGE; - - constructor( - entity: RoleEntity, - metadata: Record = {} - ) { - super(entity, metadata); - } -} - -/** - * 标签可编辑项 - */ -export class TagItem extends EditItem { - type: ItemType.TEXT = ItemType.TEXT; - - constructor( - entity: TagValueObject, - metadata: Record = {} - ) { - super(entity, metadata); - } -} - /** * 场景可编辑项 */ diff --git a/app/service/usecase/ShotEditUsecase.ts b/app/service/usecase/ShotEditUsecase.ts index aa1f75b..a2c0b74 100644 --- a/app/service/usecase/ShotEditUsecase.ts +++ b/app/service/usecase/ShotEditUsecase.ts @@ -56,16 +56,24 @@ export class VideoSegmentEditUseCase { * @returns VideoSegmentEntity[] 匹配后的视频片段列表 * @throws Error 当有视频片段未匹配到video_id时 */ + /** + * @description 为视频片段匹配对应的video_id,并输出调试信息 + * @param segments 视频片段列表 + * @param detail 项目详情数据 + * @returns VideoSegmentEntity[] 匹配后的视频片段列表 + * @throws Error 当有视频片段未匹配到video_id时 + */ private matchVideoSegmentsWithIds( segments: VideoSegmentEntity[], detail: VideoFlowProjectResponse ): VideoSegmentEntity[] { + console.log('[matchVideoSegmentsWithIds] 输入segments:', segments); + console.log('[matchVideoSegmentsWithIds] 输入detail:', detail); - - const projectData = detail.data as any; - const videoData = projectData?.data?.video?.data; + const videoData = detail?.data?.video?.data; if (!videoData || !Array.isArray(videoData)) { + console.log('[matchVideoSegmentsWithIds] videoData无效,直接返回原segments'); return segments; } @@ -78,6 +86,7 @@ export class VideoSegmentEditUseCase { }); } }); + console.log('[matchVideoSegmentsWithIds] urlToVideoMap keys:', Array.from(urlToVideoMap.keys())); // 为每个视频片段匹配video_id并重新创建实体 const updatedSegments: VideoSegmentEntity[] = []; @@ -95,12 +104,15 @@ export class VideoSegmentEditUseCase { id: videoItem.video_id }; updatedSegments.push(updatedSegment); + console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" 匹配到 video_id:`, videoItem.video_id); } else { // 如果没有匹配到,保持原样 updatedSegments.push(segment); + console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" 未匹配到video_id, 保持原样`); } } else { updatedSegments.push(segment); + console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" videoUrl无效, 保持原样`); } }); @@ -110,10 +122,12 @@ export class VideoSegmentEditUseCase { ); if (unmatchedSegments.length > 0) { - console.warn('以下视频片段未匹配到video_id:', unmatchedSegments.map(s => ({ name: s.name, videoUrl: s.videoUrl }))); - throw new Error(`有 ${unmatchedSegments.length} 个视频片段未匹配到对应的video_id`); + console.warn('[matchVideoSegmentsWithIds] 以下视频片段未匹配到video_id:', unmatchedSegments.map(s => ({ name: s.name, videoUrl: s.videoUrl }))); + // throw new Error(`有 ${unmatchedSegments.length} 个视频片段未匹配到对应的video_id`); } + console.log('[matchVideoSegmentsWithIds] 匹配后segments:', updatedSegments); + return updatedSegments; } @@ -332,3 +346,131 @@ export class VideoSegmentEditUseCase { } } } + + + + + +/** + * 边界框坐标信息 + * @description 描述检测到的人物在图片中的位置和尺寸 + */ +export interface BoundingBox { + /** X坐标(像素) */ + x: number; + /** Y坐标(像素) */ + y: number; + /** 宽度(像素) */ + width: number; + /** 高度(像素) */ + height: number; +} + +/** + * 匹配的人物信息 + * @description 从图片中识别出的与已知角色匹配的人物信息 + */ +export interface MatchedPerson { + /** 人物ID */ + person_id: string; + /** 边界框信息 */ + bbox: BoundingBox; + /** 匹配置信度(0-1之间) */ + confidence: number; +} + +/** + * 角色识别结果数据 + * @description AI识别图片中人物后的详细结果 + */ +export interface RecognitionResultData { + /** 目标图片URL */ + target_image_url: string; + /** 检测到的总人数 */ + total_persons_detected: number; + /** 匹配的人物列表 */ + matched_persons: MatchedPerson[]; +} + +/** + * 角色识别响应结果 + * @description API返回的角色识别完整响应 + */ +export interface RecognitionResult { + /** 响应状态码 */ + code: number; + /** 响应消息 */ + message: string; + /** 识别结果数据 */ + data: RecognitionResultData; +} + +/** + * 使用的角色信息 + * @description 在识别过程中使用的已知角色信息 + */ +export interface CharacterUsed { + /** 角色名称 */ + character_name: string; + /** 角色C-ID */ + c_id: string; + /** 角色图片路径 */ + image_path: string; + /** 角色头像 */ + avatar: string; +} + +/** + * 角色识别API响应类型 + * @description 完整的角色识别API响应结构 + */ +export interface RoleRecognitionResponse { + /** 项目ID */ + project_id: string; + /** 视频ID */ + video_id: string; + /** 目标图片URL */ + target_image_url: string; + /** 已知人物数量 */ + known_persons_count: number; + /** 识别结果 */ + recognition_result: RecognitionResult; + /** 使用的角色列表 */ + characters_used: CharacterUsed[]; +} + +const roleRecognitionResponse:RoleRecognitionResponse = { + "project_id": "d0df7120-e27b-4f84-875c-e532f1bd318c", + "video_id": "984f3347-c81c-4af8-9145-49ead82becde", + "target_image_url": "https://cdn.qikongjian.com/videos/1754970412744_kqxplx.png", + "known_persons_count": 1, + "recognition_result": { + "code": 200, + "message": "识别完成", + "data": + { + "target_image_url": "https://cdn.qikongjian.com/videos/1754970412744_kqxplx.png", + "total_persons_detected": 1, + "matched_persons": [ + { + "person_id": "CH-01", + "bbox": { + "x": 269, + "y": 23, + "width": 585, + "height": 685 + }, + "confidence": 0.36905956268310547 + } + ] + } + }, + "characters_used": [ + { + "character_name": "CH-01", + "c_id": "无C-ID", + "image_path": "无image_path", + "avatar": "https://cdn.huiying.video/template/Whisk_9afb196368.jpg" + } + ] +} diff --git a/components/ui/person-detection.tsx b/components/ui/person-detection.tsx index 271287f..d9298af 100644 --- a/components/ui/person-detection.tsx +++ b/components/ui/person-detection.tsx @@ -54,10 +54,10 @@ type Props = { onPersonClick?: (person: PersonDetection) => void; }; -export const PersonDetectionScene: React.FC = ({ - backgroundImage, - videoSrc, - detections, +export const PersonDetectionScene: React.FC = ({ + backgroundImage, + videoSrc, + detections, triggerScan, onScanStart, onScanTimeout, @@ -192,6 +192,7 @@ export const PersonDetectionScene: React.FC = ({ className="absolute inset-0 w-full h-full object-cover z-0" autoPlay muted={false} + crossOrigin="anonymous" loop controls /> @@ -245,7 +246,7 @@ export const PersonDetectionScene: React.FC = ({ ) : ( = ({ {/* 人物识别框和浮签 */} {detections.map((person, index) => { - + return (
{ onPersonClick?.(person); diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx index 1bfc3b5..a64a2f4 100644 --- a/components/ui/shot-tab-content.tsx +++ b/components/ui/shot-tab-content.tsx @@ -45,7 +45,7 @@ export function ShotTabContent({ if (shotData.length > 0) { setSelectedSegment(shotData[selectedIndex]); } - }, [selectedIndex]); + }, [selectedIndex, shotData]); // 处理扫描开始 const handleScan = () => { @@ -101,7 +101,7 @@ export function ShotTabContent({ // 确认替换角色 const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => { - + }; // 点击按钮重新生成 @@ -252,7 +252,7 @@ export function ShotTabContent({ ); })} - + {/* 渐变遮罩 */}
@@ -261,7 +261,7 @@ export function ShotTabContent({ {/* 下部分 */} - handleScan()} className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full - ${scanState === 'detected' - ? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white' + ${scanState === 'detected' + ? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white' : 'bg-black/50 hover:bg-black/70 text-white' }`} whileHover={{ scale: 1.05 }} @@ -336,7 +336,7 @@ export function ShotTabContent({
handleAddShot()} - className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-500/10 hover:bg-pink-500/20 + className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-500/10 hover:bg-pink-500/20 text-pink-500 rounded-lg transition-colors" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} @@ -346,7 +346,7 @@ export function ShotTabContent({ handleRegenerate()} - className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20 + className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20 text-blue-500 rounded-lg transition-colors" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} @@ -357,7 +357,7 @@ export function ShotTabContent({
- +
); -} \ No newline at end of file +}