forked from 77media/video-flow
推进
This commit is contained in:
parent
b0e15c16c0
commit
f41f3b35e4
@ -209,7 +209,11 @@ export interface CreateMovieProjectV3Request {
|
|||||||
/**角色名 */
|
/**角色名 */
|
||||||
role_name: string;
|
role_name: string;
|
||||||
/**角色描述 */
|
/**角色描述 */
|
||||||
role_description: string;
|
role_description: {
|
||||||
|
name: string;
|
||||||
|
image_url: string;
|
||||||
|
character_analysis: Record<string, any>;
|
||||||
|
};
|
||||||
/**照片URL */
|
/**照片URL */
|
||||||
photo_url: string;
|
photo_url: string;
|
||||||
/**声音URL */
|
/**声音URL */
|
||||||
|
|||||||
@ -126,12 +126,11 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const character_briefs = {
|
const character_brief = {
|
||||||
name: selectedTemplate.storyRole[activeRoleIndex].role_name,
|
name: selectedTemplate.storyRole[activeRoleIndex].role_name,
|
||||||
image_url: imageUrl,
|
image_url: imageUrl,
|
||||||
character_analysis: JSON.parse(desc).character_analysis,
|
character_analysis: JSON.parse(desc).character_analysis,
|
||||||
};
|
};
|
||||||
console.log("character_briefs", character_briefs);
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
storyRole: selectedTemplate.storyRole.map((role, index) =>
|
storyRole: selectedTemplate.storyRole.map((role, index) =>
|
||||||
@ -139,7 +138,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
|||||||
? {
|
? {
|
||||||
...role,
|
...role,
|
||||||
photo_url: imageUrl,
|
photo_url: imageUrl,
|
||||||
role_description: JSON.stringify(character_briefs),
|
role_description: character_brief,
|
||||||
}
|
}
|
||||||
: role
|
: role
|
||||||
),
|
),
|
||||||
|
|||||||
@ -146,8 +146,12 @@ export interface StoryTemplateEntity {
|
|||||||
storyRole: {
|
storyRole: {
|
||||||
/**角色名 */
|
/**角色名 */
|
||||||
role_name: string;
|
role_name: string;
|
||||||
/**角色描述 */
|
/**角色描述 对应后端是单个的character_brief */
|
||||||
role_description: string;
|
role_description: {
|
||||||
|
name: string;
|
||||||
|
image_url: string;
|
||||||
|
character_analysis: Record<string, any>;
|
||||||
|
};
|
||||||
/**照片URL */
|
/**照片URL */
|
||||||
photo_url: string;
|
photo_url: string;
|
||||||
/**声音URL */
|
/**声音URL */
|
||||||
|
|||||||
@ -72,6 +72,44 @@ const audioRecorderStyles = `
|
|||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 音量滑块样式 */
|
||||||
|
.volume-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-webkit-slider-track {
|
||||||
|
background: transparent;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
box-shadow: -100px 0 5px 100px #92ff77, -100px 0px 20px 100px #92ff77;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-moz-range-track {
|
||||||
|
background: transparent;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-moz-range-thumb {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: -100px 0 5px 100px #92ff77, -100px 0px 20px 100px #92ff77;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface AudioRecorderProps {
|
interface AudioRecorderProps {
|
||||||
@ -229,8 +267,11 @@ export function AudioRecorder({
|
|||||||
// 音量调节
|
// 音量调节
|
||||||
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newVolume = parseFloat(e.target.value);
|
const newVolume = parseFloat(e.target.value);
|
||||||
setVolume(newVolume);
|
// 确保音量值在 0-1 范围内
|
||||||
if (isMuted && newVolume > 0) {
|
const clampedVolume = Math.max(0, Math.min(1, newVolume));
|
||||||
|
console.log("newVolume", newVolume, "clampedVolume", clampedVolume);
|
||||||
|
setVolume(clampedVolume);
|
||||||
|
if (isMuted && clampedVolume > 0) {
|
||||||
setIsMuted(false);
|
setIsMuted(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -446,7 +487,7 @@ export function AudioRecorder({
|
|||||||
data-alt="audio-player-container"
|
data-alt="audio-player-container"
|
||||||
>
|
>
|
||||||
{/* 大旋转的播放圆盘 */}
|
{/* 大旋转的播放圆盘 */}
|
||||||
<div className=" absolute z-1 right-6 top-[50%] translate-y-[-50%] scale-[1.4]">
|
<div className=" absolute z-10 right-10 top-[45%] translate-y-[-50%] scale-[1.4]">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* 外层边框 - 唱片边缘 */}
|
{/* 外层边框 - 唱片边缘 */}
|
||||||
<div
|
<div
|
||||||
@ -460,7 +501,7 @@ export function AudioRecorder({
|
|||||||
|
|
||||||
{/* 唱片主体 - 纯黑色唱片面 */}
|
{/* 唱片主体 - 纯黑色唱片面 */}
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-2 w-24 h-24 rounded-full bg-black shadow-[inset_0_2px_8px_rgba(0,0,0,0.8)] ${
|
className={`absolute inset-2 w-24 h-24 rounded-full bg-[0,0,0,.4] shadow-[inset_0_2px_8px_rgba(0,0,0,0.8)] ${
|
||||||
isPlaying ? "animate-spin" : ""
|
isPlaying ? "animate-spin" : ""
|
||||||
}`}
|
}`}
|
||||||
style={{ animationDuration: "6s" }}
|
style={{ animationDuration: "6s" }}
|
||||||
@ -526,9 +567,19 @@ export function AudioRecorder({
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* 播放控制 */}
|
||||||
|
<button
|
||||||
|
onClick={togglePlay}
|
||||||
|
className="absolute z-10 right-[4.75rem] top-[45%] translate-y-[-50%] flex items-center justify-center w-10 h-10 bg-gradient-to-br from-[rgb(106,244,249)] to-[rgb(199,59,255)] text-white rounded-full shadow-[inset_0_1px_0_rgba(255,255,255,0.3),0_4px_12px_rgba(0,0,0,0.3)] hover:shadow-[inset_0_1px_0_rgba(255,255,255,0.4),0_6px_20px_rgba(106,244,249,0.4)] transition-all duration-300 hover:scale-105 active:scale-95"
|
||||||
|
>
|
||||||
|
{isPlaying ? (
|
||||||
|
<Pause className="w-5 h-5 drop-shadow-sm" />
|
||||||
|
) : (
|
||||||
|
<Play className="w-5 h-5 drop-shadow-sm ml-0.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
{/* 头部 - 只显示操作按钮 */}
|
{/* 头部 - 只显示操作按钮 */}
|
||||||
<div className="relative z-10 flex justify-end gap-2 mb-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
<div className="relative -right-3 z-10 flex justify-end gap-2 mb-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="text-white/60 hover:text-red-400 transition-colors"
|
className="text-white/60 hover:text-red-400 transition-colors"
|
||||||
@ -547,62 +598,19 @@ export function AudioRecorder({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 播放控制 */}
|
|
||||||
<div className="relative z-10 flex items-center justify-center gap-4 mb-4 mt-12">
|
|
||||||
<button
|
|
||||||
onClick={togglePlay}
|
|
||||||
className="flex items-center justify-center w-12 h-12 bg-gradient-to-br from-[rgb(106,244,249)] to-[rgb(199,59,255)] text-white rounded-xl shadow-[inset_0_1px_0_rgba(255,255,255,0.3),0_4px_12px_rgba(0,0,0,0.3)] hover:shadow-[inset_0_1px_0_rgba(255,255,255,0.4),0_6px_20px_rgba(106,244,249,0.4)] transition-all duration-300 hover:scale-105 active:scale-95"
|
|
||||||
>
|
|
||||||
{isPlaying ? (
|
|
||||||
<Pause className="w-5 h-5 drop-shadow-sm" />
|
|
||||||
) : (
|
|
||||||
<Play className="w-5 h-5 drop-shadow-sm ml-0.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
{/* WaveSurfer 波形图区域 */}
|
|
||||||
<div className="h-16 flex-1 bg-white/[0.05] rounded-lg overflow-hidden">
|
|
||||||
<WaveformPlayer
|
|
||||||
audioUrl={audioUrl}
|
|
||||||
isPlaying={isPlaying}
|
|
||||||
onPlayStateChange={setIsPlaying}
|
|
||||||
volume={volume}
|
|
||||||
isMuted={isMuted}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 音频设置 */}
|
{/* 音频设置 */}
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="relative z-1 flex items-center justify-between px-2 mt-16 mb-2">
|
||||||
<div className="flex items-center gap-6">
|
<VolumeSlider volume={volume} onVolumeChange={handleVolumeChange} />
|
||||||
{/* 音量控制 */}
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
{/* WaveSurfer 波形图区域 */}
|
||||||
<div className="w-24 h-3 bg-white/[0.08] rounded-full overflow-hidden shadow-inner border border-white/[0.1] relative">
|
<div className=" relative z-5 h-16 flex-1 bg-white/[0.05] rounded-lg overflow-hidden">
|
||||||
<div
|
<WaveformPlayer
|
||||||
className="h-full bg-gradient-to-r from-[rgb(106,244,249)] to-[rgb(199,59,255)] rounded-full transition-all duration-300"
|
audioUrl={audioUrl}
|
||||||
style={{ width: `${volume * 100}%` }}
|
isPlaying={isPlaying}
|
||||||
/>
|
onPlayStateChange={setIsPlaying}
|
||||||
{/* 可拖拽的音量滑块 */}
|
volume={volume}
|
||||||
<input
|
isMuted={isMuted}
|
||||||
type="range"
|
/>
|
||||||
min="0"
|
|
||||||
max="1"
|
|
||||||
step="0.01"
|
|
||||||
value={volume}
|
|
||||||
onChange={handleVolumeChange}
|
|
||||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
||||||
/>
|
|
||||||
{/* 视觉滑块指示器 */}
|
|
||||||
<div
|
|
||||||
className="absolute top-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full shadow-lg border-2 border-[rgb(199,59,255)] pointer-events-none transition-all duration-200"
|
|
||||||
style={{ left: `calc(${volume * 100}% - 8px)` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* 音频图标 */}
|
|
||||||
<div className="text-white/70 hover:text-white/90 transition-colors">
|
|
||||||
<Volume2 className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -721,3 +729,130 @@ function WaveformPlayer({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function VolumeSlider({
|
||||||
|
volume,
|
||||||
|
onVolumeChange,
|
||||||
|
}: {
|
||||||
|
volume: number;
|
||||||
|
onVolumeChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
}) {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full">
|
||||||
|
<style jsx>{`
|
||||||
|
/* From Uiverse.io by javierBarroso */
|
||||||
|
/* level settings 👇 */
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
/* slider */
|
||||||
|
--slider-width: 60%;
|
||||||
|
--slider-height: 12px;
|
||||||
|
--slider-bg: rgba(82, 82, 82, 0.322);
|
||||||
|
--slider-border-radius: 4px;
|
||||||
|
/* level */
|
||||||
|
--level-color: rgb(106,244,249);
|
||||||
|
--level-transition-duration: 5s;
|
||||||
|
/* icon */
|
||||||
|
--icon-margin: 10px;
|
||||||
|
--icon-color: var(--slider-bg);
|
||||||
|
--icon-size: 20px;
|
||||||
|
position: relative;
|
||||||
|
left: -40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
display: -webkit-inline-box;
|
||||||
|
display: -ms-inline-flexbox;
|
||||||
|
display: inline-flex;
|
||||||
|
-webkit-box-orient: horizontal;
|
||||||
|
-webkit-box-direction: reverse;
|
||||||
|
-ms-flex-direction: row-reverse;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .volume {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: var(--icon-margin);
|
||||||
|
color: var(--icon-color);
|
||||||
|
width: var(--icon-size);
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .level {
|
||||||
|
position: relative;
|
||||||
|
left: -30px;
|
||||||
|
top: -40px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: var(--slider-width);
|
||||||
|
height: var(--slider-height);
|
||||||
|
background: var(--slider-bg);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--slider-border-radius);
|
||||||
|
-webkit-transition: height var(--level-transition-duration);
|
||||||
|
-o-transition: height var(--level-transition-duration);
|
||||||
|
transition: height var(--level-transition-duration);
|
||||||
|
cursor: inherit;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:hover .level {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .level::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
-webkit-box-shadow: -200px 0 0 200px var(--level-color);
|
||||||
|
box-shadow: -100px 0 5px 100px var(--level-color),
|
||||||
|
-100px 0px 20px 100px var(--level-color);
|
||||||
|
}
|
||||||
|
.slider .level:hover ~ .volume {
|
||||||
|
color: var(--level-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .level::-moz-range-thumb {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: -100px 0 5px 100px var(--level-color),
|
||||||
|
-100px 0px 20px 100px var(--level-color);
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<label
|
||||||
|
className="slider"
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
className="level"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.01"
|
||||||
|
value={volume}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
/>
|
||||||
|
<Volume2 className="text-[#777876]" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -365,7 +365,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
<GlobalLoad show={isLoading} progress={0}>
|
<GlobalLoad show={isLoading} progress={0}>
|
||||||
<div className="rounded-2xl min-h-min transition-all duration-700 ease-out">
|
<div className="rounded-2xl min-h-min transition-all duration-700 ease-out">
|
||||||
{/* 弹窗头部 */}
|
{/* 弹窗头部 */}
|
||||||
<div className="flex gap-4 p-4 border-b border-white/[0.1]">
|
<div className="flex gap-4 px-4 pb-2 border-b border-white/[0.1]">
|
||||||
<h2 className="text-2xl font-bold text-white">
|
<h2 className="text-2xl font-bold text-white">
|
||||||
Template Story Selection
|
Template Story Selection
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user