diff --git a/components/ui/FloatingGlassPanel.tsx b/components/ui/FloatingGlassPanel.tsx
index fcf8496..0564a75 100644
--- a/components/ui/FloatingGlassPanel.tsx
+++ b/components/ui/FloatingGlassPanel.tsx
@@ -9,9 +9,10 @@ type FloatingGlassPanelProps = {
children: ReactNode;
width?: string;
r_key?: string | number;
+ panel_style?: React.CSSProperties;
};
-export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key }: FloatingGlassPanelProps) {
+export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key, panel_style }: FloatingGlassPanelProps) {
// 定义弹出动画
const bounceAnimation = {
scale: [0.95, 1.02, 0.98, 1],
@@ -48,7 +49,7 @@ export default function FloatingGlassPanel({ open, onClose, children, width = '3
}}
>
{children}
diff --git a/components/ui/ImageWave.tsx b/components/ui/ImageWave.tsx
index 60feecf..2f4e1d8 100644
--- a/components/ui/ImageWave.tsx
+++ b/components/ui/ImageWave.tsx
@@ -1,5 +1,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import styled from 'styled-components';
+import { GlassIconButton } from '@/components/ui/glass-icon-button';
+import { Check } from 'lucide-react';
interface ImageWaveProps {
// 图片列表数据
@@ -152,8 +154,6 @@ export const ImageWave: React.FC
= ({
setCurrentExpandedItem(null);
} else {
setCurrentExpandedItem(index);
- setCurrentSelectedIndex(index);
- onClick?.(index);
}
};
@@ -188,6 +188,11 @@ export const ImageWave: React.FC = ({
};
}, [autoAnimate]);
+ const handleSelectImage = (index: number) => {
+ setCurrentSelectedIndex(index);
+ onClick?.(index);
+ };
+
return (
@@ -196,11 +201,19 @@ export const ImageWave: React.FC = ({
key={index}
width={itemWidth}
height={itemHeight}
- className={`item ${currentExpandedItem === index ? 'expanded' : ''} ${currentSelectedIndex === index ? 'selected' : ''}`}
+ className={`group relative item ${currentExpandedItem === index ? 'expanded' : ''} ${currentSelectedIndex === index ? 'selected' : ''}`}
style={{ backgroundImage: `url(${image})` }}
onClick={() => handleItemClick(index)}
tabIndex={0}
- />
+ >
+ {/* 添加一个玻璃按钮 勾选当前图片 移入/选中改变图标颜色 */}
+ handleSelectImage(index)}
+ className="absolute top-1 right-1 z-[999] cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity duration-300 group-hover:bg-blue-500/50"
+ />
+
))}
diff --git a/components/ui/character-library-selector.tsx b/components/ui/character-library-selector.tsx
new file mode 100644
index 0000000..e8e01f9
--- /dev/null
+++ b/components/ui/character-library-selector.tsx
@@ -0,0 +1,57 @@
+import FloatingGlassPanel from './FloatingGlassPanel';
+import { ImageWave } from '@/components/ui/ImageWave';
+
+const imageUrls = [
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
+ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
+];
+
+export function CharacterLibrarySelector({
+ isReplaceLibraryOpen,
+ setIsReplaceLibraryOpen,
+ onSelect,
+}: {
+ isReplaceLibraryOpen: boolean;
+ setIsReplaceLibraryOpen: (open: boolean) => void;
+ onSelect: (index: number) => void;
+}) {
+ return (
+ setIsReplaceLibraryOpen(false)}
+ >
+ {/* 内容 */}
+ {
+ onSelect(index);
+ }}
+ />
+
+ );
+}
\ No newline at end of file
diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx
index 6f6be3e..42d7204 100644
--- a/components/ui/character-tab-content.tsx
+++ b/components/ui/character-tab-content.tsx
@@ -2,14 +2,11 @@ import React, { useState, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Upload, Library, Play, Pause, RefreshCw, Wand2, Users, Check, ReplaceAll, X } from 'lucide-react';
import { cn } from '@/public/lib/utils';
-import { GlassIconButton } from './glass-icon-button';
-import { ReplaceCharacterModal } from './replace-character-modal';
-import { Slider } from './slider';
import CharacterEditor from './character-editor';
import ImageBlurTransition from './ImageBlurTransition';
import FloatingGlassPanel from './FloatingGlassPanel';
import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel';
-import { ImageWave } from '@/components/ui/ImageWave';
+import { CharacterLibrarySelector } from './character-library-selector';
interface Appearance {
hairStyle: string;
@@ -58,45 +55,19 @@ interface CharacterTabContentProps {
roles: Role[];
}
-const imageUrls = [
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg',
- 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg',
-];
-
export function CharacterTabContent({
taskSketch,
currentRoleIndex,
onSketchSelect,
roles = [mockRole]
}: CharacterTabContentProps) {
- const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false);
- const [activeReplaceMethod, setActiveReplaceMethod] = useState('upload');
- const [newTag, setNewTag] = useState('');
const [localRole, setLocalRole] = useState(mockRole);
const [currentRole, setCurrentRole] = useState(roles[currentRoleIndex]);
const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
const [replacePanelKey, setReplacePanelKey] = useState(0);
const [ignoreReplace, setIgnoreReplace] = useState(false);
const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false);
- const [replaceLibraryKey, setReplaceLibraryKey] = useState(0);
- const textareaRef = useRef(null);
const handleReplaceCharacter = (url: string) => {
setCurrentRole({
@@ -133,6 +104,13 @@ export function CharacterTabContent({
setCurrentRole(roles[index]);
};
+ // 从角色库中选择角色
+ const handleSelectCharacter = (index: number) => {
+ console.log('index', index);
+ setIsReplaceLibraryOpen(false);
+ handleReplaceCharacter('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg');
+ };
+
// 如果没有角色数据,显示占位内容
if (!roles || roles.length === 0) {
return (
@@ -260,7 +238,7 @@ export function CharacterTabContent({
{/* 重新生成按钮、替换形象按钮 */}
console.log('Replace')}
+ onClick={() => handleReplaceCharacter('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg')}
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 }}
@@ -287,7 +265,12 @@ export function CharacterTabContent({
-
+ handleCloseReplacePanel()}
+ >
{/* 从角色库中选择角色 */}
-
- {/* 标题 从角色库中选择角色 */}
- Role Library
- {/* 内容 */}
- {
- console.log('index', index);
- }}
- />
- {/* 操作按钮 */}
-
-
-
-
-
+
);
}
\ No newline at end of file
diff --git a/components/ui/person-detection.tsx b/components/ui/person-detection.tsx
index 7c41fd5..8274d09 100644
--- a/components/ui/person-detection.tsx
+++ b/components/ui/person-detection.tsx
@@ -51,6 +51,7 @@ type Props = {
scanTimeout?: number;
isScanFailed?: boolean; // 外部传入的失败状态
onDetectionsChange?: (detections: PersonDetection[]) => void;
+ onPersonClick?: (person: PersonDetection) => void;
};
export const PersonDetectionScene: React.FC = ({
@@ -63,7 +64,8 @@ export const PersonDetectionScene: React.FC = ({
onScanExit,
scanTimeout = 10000,
isScanFailed = false,
- onDetectionsChange
+ onDetectionsChange,
+ onPersonClick
}) => {
const scanControls = useAnimation();
const videoRef = useRef(null);
@@ -374,7 +376,9 @@ export const PersonDetectionScene: React.FC = ({
{detections.map((person, index) => {
return (
-
+ {
+ onPersonClick?.(person);
+ }}>
= ({
>
{person.name}
-
+
);
})}
diff --git a/components/ui/replace-character-panel.tsx b/components/ui/replace-character-panel.tsx
index 498a417..211ed01 100644
--- a/components/ui/replace-character-panel.tsx
+++ b/components/ui/replace-character-panel.tsx
@@ -4,6 +4,7 @@ import { Shot, Character } from '@/app/model/types';
interface ReplaceCharacterPanelProps {
shots: Shot[];
character: Character;
+ showAddToLibrary?: boolean;
onClose: () => void;
onConfirm: (selectedShots: string[], addToLibrary: boolean) => void;
}
@@ -47,6 +48,7 @@ export const mockCharacter: Character = {
export function ReplaceCharacterPanel({
shots = mockShots,
character = mockCharacter,
+ showAddToLibrary = true,
onClose,
onConfirm,
}: ReplaceCharacterPanelProps) {
@@ -55,7 +57,7 @@ export function ReplaceCharacterPanel({
title="替换新形象"
shots={shots}
item={character}
- showAddToLibrary={true}
+ showAddToLibrary={showAddToLibrary}
addToLibraryText="新形象同步添加至角色库"
onClose={onClose}
onConfirm={onConfirm}
diff --git a/components/ui/replace-panel.tsx b/components/ui/replace-panel.tsx
index 08181a0..38d4fe9 100644
--- a/components/ui/replace-panel.tsx
+++ b/components/ui/replace-panel.tsx
@@ -100,14 +100,14 @@ export function ReplacePanel({
{/* 分镜展示区 */}
选择需要替换的分镜:
-
+
{shots.map((shot) => (
console.log('Replace')}
+ onClick={() => handleReplaceScene('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg')}
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 }}
diff --git a/components/ui/shot-editor/CharacterToken.tsx b/components/ui/shot-editor/CharacterToken.tsx
index 4910397..313fbff 100644
--- a/components/ui/shot-editor/CharacterToken.tsx
+++ b/components/ui/shot-editor/CharacterToken.tsx
@@ -11,13 +11,18 @@ interface CharacterAttributes {
age: string;
}
+// interface CharacterTokenProps extends ReactNodeViewProps {
+// onClick?: (attrs: CharacterAttributes) => void
+// }
+
export function CharacterToken(props: ReactNodeViewProps) {
const [showCard, setShowCard] = useState(false)
const { name, avatar, gender, age } = props.node.attrs as CharacterAttributes
const handleClick = () => {
console.log('点击角色:', name)
- alert(`点击角色:${name}`)
+ const { editor } = props;
+ editor?.emit('character-clicked', props.node.attrs);
}
return (
@@ -81,6 +86,12 @@ export const CharacterTokenExtension = Node.create({
return ['character-token', mergeAttributes(HTMLAttributes)];
},
+ // addStorage() {
+ // return {
+ // onClickCharacter: null as null | ((character: CharacterAttributes) => void),
+ // }
+ // },
+
addNodeView() {
return ReactNodeViewRenderer(CharacterToken);
},
diff --git a/components/ui/shot-editor/ShotEditor.tsx b/components/ui/shot-editor/ShotEditor.tsx
index 54a1319..e9ab0c0 100644
--- a/components/ui/shot-editor/ShotEditor.tsx
+++ b/components/ui/shot-editor/ShotEditor.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { motion } from "framer-motion";
@@ -36,9 +36,10 @@ const initialContent = {
interface ShotEditorProps {
onAddSegment?: () => void;
+ onCharacterClick?: (attrs: any) => void;
}
-const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps>(function ShotEditor({ onAddSegment }, ref) {
+const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>(function ShotEditor({ onAddSegment, onCharacterClick }, ref) {
const [segments, setSegments] = useState(initialContent.content);
const editor = useEditor({
@@ -60,6 +61,20 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps>
},
})
+ useEffect(() => {
+ const handleCharacterClick = (attrs: any) => {
+ console.log('SceneEditor 收到角色点击事件:', attrs)
+ // 你可以这里 setState 打开一个弹窗 / 面板等
+ onCharacterClick?.(attrs);
+ };
+
+ editor?.on('character-clicked', handleCharacterClick as any);
+
+ return () => {
+ editor?.off('character-clicked', handleCharacterClick as any);
+ };
+ }, [editor]);
+
const addSegment = () => {
if (!editor) return;
diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx
index 6161876..9c9b7cf 100644
--- a/components/ui/shot-tab-content.tsx
+++ b/components/ui/shot-tab-content.tsx
@@ -10,6 +10,9 @@ import { MediaPropertiesModal } from './media-properties-modal';
import { DramaLineChart } from './drama-line-chart';
import { PersonDetection, PersonDetectionScene } from './person-detection';
import ShotEditor from './shot-editor/ShotEditor';
+import { CharacterLibrarySelector } from './character-library-selector';
+import FloatingGlassPanel from './FloatingGlassPanel';
+import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel';
interface ShotTabContentProps {
taskSketch: any[];
@@ -29,16 +32,16 @@ export function ShotTabContent({
const videosRef = useRef(null);
const videoPlayerRef = useRef(null);
const [isPlaying, setIsPlaying] = React.useState(externalIsPlaying);
- const [isMuted, setIsMuted] = React.useState(false);
- const [progress, setProgress] = React.useState(0);
- const [isReplaceModalOpen, setIsReplaceModalOpen] = React.useState(false);
- const [activeReplaceMethod, setActiveReplaceMethod] = React.useState<'upload' | 'library' | 'generate'>('upload');
const [isMediaPropertiesModalOpen, setIsMediaPropertiesModalOpen] = React.useState(false);
- const [triggerScan, setTriggerScan] = useState(false);
const [detections, setDetections] = useState([]);
const [scanState, setScanState] = useState<'idle' | 'scanning' | 'detected'>('idle');
+ const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false);
+ const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
+
+ const [shots, setShots] = useState([]);
+
// 监听外部播放状态变化
useEffect(() => {
@@ -87,13 +90,6 @@ export function ShotTabContent({
}
}, [isPlaying, currentSketchIndex]);
- // 更新进度条
- const handleTimeUpdate = () => {
- if (videoPlayerRef.current) {
- const progress = (videoPlayerRef.current.currentTime / videoPlayerRef.current.duration) * 100;
- setProgress(progress);
- }
- };
// 处理扫描开始
const handleScan = () => {
@@ -130,6 +126,27 @@ export function ShotTabContent({
}
};
+ // 处理人物点击 打开角色库
+ const handlePersonClick = (person: PersonDetection) => {
+ console.log('person', person);
+ setIsReplaceLibraryOpen(true);
+ };
+
+ // 从角色库中选择角色
+ const handleSelectCharacter = (index: number) => {
+ console.log('index', index);
+ setIsReplaceLibraryOpen(false);
+ // 模拟打开替换面板
+ setTimeout(() => {
+ setIsReplacePanelOpen(true);
+ }, 1000);
+ };
+
+ // 确认替换角色
+ const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
+
+ };
+
// 如果没有数据,显示空状态
if (sketches.length === 0) {
return (
@@ -176,14 +193,14 @@ export function ShotTabContent({
Shot {index + 1}
{/* 鼠标悬浮/移出 显示/隐藏 删除图标 */}
-
+ {/*
-
+
*/}
))}
@@ -251,6 +268,7 @@ export function ShotTabContent({
onScanTimeout={handleScanTimeout}
onScanExit={handleScanTimeout}
onDetectionsChange={handleDetectionsChange}
+ onPersonClick={handlePersonClick}
/>
{/*