forked from 77media/video-flow
加入视频比例配置
This commit is contained in:
parent
c9f9a0030b
commit
170a8b7a4f
@ -106,6 +106,8 @@ export interface CreateMovieProjectV2Request {
|
|||||||
language: string;
|
language: string;
|
||||||
/** 图片URL */
|
/** 图片URL */
|
||||||
image_url: string;
|
image_url: string;
|
||||||
|
/** 画面比例(横/竖屏) */
|
||||||
|
aspect_ratio?: "16:9" | "9:16";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,6 +251,8 @@ export interface CreateMovieProjectV3Request {
|
|||||||
/** 道具照片URL */
|
/** 道具照片URL */
|
||||||
photo_url: string;
|
photo_url: string;
|
||||||
}[];
|
}[];
|
||||||
|
/** 画面比例(横/竖屏) */
|
||||||
|
aspect_ratio?: "16:9" | "9:16";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -57,7 +57,8 @@ interface UseImageStoryService {
|
|||||||
user_id: string,
|
user_id: string,
|
||||||
mode?: "auto" | "manual",
|
mode?: "auto" | "manual",
|
||||||
resolution?: "720p" | "1080p" | "4k",
|
resolution?: "720p" | "1080p" | "4k",
|
||||||
language?: string
|
language?: string,
|
||||||
|
aspectRatio?: "16:9" | "9:16"
|
||||||
) => Promise<{ project_id: string } | undefined>;
|
) => Promise<{ project_id: string } | undefined>;
|
||||||
/** 设置角色分析 */
|
/** 设置角色分析 */
|
||||||
setCharactersAnalysis: Dispatch<SetStateAction<CharacterAnalysis[]>>;
|
setCharactersAnalysis: Dispatch<SetStateAction<CharacterAnalysis[]>>;
|
||||||
@ -457,7 +458,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
|||||||
user_id: string,
|
user_id: string,
|
||||||
mode: "auto" | "manual" = "auto",
|
mode: "auto" | "manual" = "auto",
|
||||||
resolution: "720p" | "1080p" | "4k" = "720p",
|
resolution: "720p" | "1080p" | "4k" = "720p",
|
||||||
language: string = "English"
|
language: string = "English",
|
||||||
|
aspectRatio?: "16:9" | "9:16"
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
if (hasAnalyzed) {
|
if (hasAnalyzed) {
|
||||||
@ -480,7 +482,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
|||||||
character_briefs,
|
character_briefs,
|
||||||
language,
|
language,
|
||||||
image_url: activeImageUrl,
|
image_url: activeImageUrl,
|
||||||
project_id:taskId
|
project_id:taskId,
|
||||||
|
...(aspectRatio ? { aspect_ratio: aspectRatio } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
// 调用create_movie_project_v2接口
|
// 调用create_movie_project_v2接口
|
||||||
|
|||||||
@ -28,7 +28,8 @@ interface UseTemplateStoryService {
|
|||||||
user_id: string,
|
user_id: string,
|
||||||
mode: "auto" | "manual",
|
mode: "auto" | "manual",
|
||||||
resolution: "720p" | "1080p" | "4k",
|
resolution: "720p" | "1080p" | "4k",
|
||||||
language: string
|
language: string,
|
||||||
|
aspectRatio?: "16:9" | "9:16"
|
||||||
) => Promise<string | undefined>;
|
) => Promise<string | undefined>;
|
||||||
/** 设置选中的模板 */
|
/** 设置选中的模板 */
|
||||||
setSelectedTemplate: (template: StoryTemplateEntity | null) => void;
|
setSelectedTemplate: (template: StoryTemplateEntity | null) => void;
|
||||||
@ -250,7 +251,8 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
|||||||
user_id: string,
|
user_id: string,
|
||||||
mode: "auto" | "manual" = "auto",
|
mode: "auto" | "manual" = "auto",
|
||||||
resolution: "720p" | "1080p" | "4k" = "720p",
|
resolution: "720p" | "1080p" | "4k" = "720p",
|
||||||
language: string = "English"
|
language: string = "English",
|
||||||
|
aspectRatio?: "16:9" | "9:16"
|
||||||
) => {
|
) => {
|
||||||
console.log('selectedTemplate', selectedTemplate)
|
console.log('selectedTemplate', selectedTemplate)
|
||||||
try {
|
try {
|
||||||
@ -271,7 +273,8 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
|||||||
storyItem: selectedTemplate?.storyItem || [],
|
storyItem: selectedTemplate?.storyItem || [],
|
||||||
freeInput: selectedTemplate?.freeInput || [],
|
freeInput: selectedTemplate?.freeInput || [],
|
||||||
language,
|
language,
|
||||||
template_id: selectedTemplate?.template_id || ""
|
template_id: selectedTemplate?.template_id || "",
|
||||||
|
...(aspectRatio ? { aspect_ratio: aspectRatio } : {})
|
||||||
};
|
};
|
||||||
console.log("params", params);
|
console.log("params", params);
|
||||||
const result = await MovieProjectService.createProject(
|
const result = await MovieProjectService.createProject(
|
||||||
|
|||||||
86
components/ChatInputBox/AspectRatioSelector.tsx
Normal file
86
components/ChatInputBox/AspectRatioSelector.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Dropdown } from "antd";
|
||||||
|
import { RectangleHorizontal, RectangleVertical } from "lucide-react";
|
||||||
|
import { AspectRatioOptions } from "./types";
|
||||||
|
|
||||||
|
export type AspectRatioValue =
|
||||||
|
| "VIDEO_ASPECT_RATIO_LANDSCAPE"
|
||||||
|
| "VIDEO_ASPECT_RATIO_PORTRAIT";
|
||||||
|
|
||||||
|
interface AspectRatioSelectorProps {
|
||||||
|
/** Current selected aspect ratio value */
|
||||||
|
value: AspectRatioValue;
|
||||||
|
/** Change handler when an option is selected */
|
||||||
|
onChange: (value: AspectRatioValue) => void;
|
||||||
|
/** Optional className to customize the trigger button */
|
||||||
|
className?: string;
|
||||||
|
/** Optional dropdown placement, defaults to top */
|
||||||
|
placement?: "top" | "bottom" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
||||||
|
/** data-alt tag for analytics/testing */
|
||||||
|
dataAlt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable aspect ratio selector (landscape/portrait) using Antd Dropdown.
|
||||||
|
* Shows an icon and label, and calls onChange when a new ratio is chosen.
|
||||||
|
* @param {AspectRatioValue} value - current selected value
|
||||||
|
* @param {(v: AspectRatioValue) => void} onChange - change handler
|
||||||
|
* @param {string} [className] - optional className for trigger button
|
||||||
|
* @param {string} [placement] - Dropdown placement, default is top
|
||||||
|
* @param {string} [dataAlt] - data-alt attribute for the trigger
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export const AspectRatioSelector = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
placement = "top",
|
||||||
|
dataAlt = "config-aspect-ratio",
|
||||||
|
}: AspectRatioSelectorProps) => {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
overlayClassName="aspect-dropdown"
|
||||||
|
menu={{
|
||||||
|
items: AspectRatioOptions.map((option) => ({
|
||||||
|
key: option.value,
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-2 px-2 py-2 ${
|
||||||
|
option.value === value ? "bg-white/[0.12] rounded-md" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option.value === "VIDEO_ASPECT_RATIO_LANDSCAPE" ? (
|
||||||
|
<RectangleHorizontal className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<RectangleVertical className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
<span className="text-sm text-white">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
onClick: ({ key }) => onChange(key as AspectRatioValue),
|
||||||
|
}}
|
||||||
|
trigger={["click"]}
|
||||||
|
placement={placement}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-alt={dataAlt}
|
||||||
|
className={`flex items-center gap-1 text-white/80 transition-all duration-200 px-2 py-2 ${className || ""}`}
|
||||||
|
>
|
||||||
|
{value === "VIDEO_ASPECT_RATIO_LANDSCAPE" ? (
|
||||||
|
<RectangleHorizontal className={"w-4 h-4"} />
|
||||||
|
) : (
|
||||||
|
<RectangleVertical className={"w-4 h-4"} />
|
||||||
|
)}
|
||||||
|
<span className="text-sm">
|
||||||
|
{value === "VIDEO_ASPECT_RATIO_LANDSCAPE" ? "16:9" : "9:16"}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AspectRatioSelector;
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +50,7 @@ import { PcTemplateModal } from "./PcTemplateModal";
|
|||||||
import { H5TemplateDrawer } from "./H5TemplateDrawer";
|
import { H5TemplateDrawer } from "./H5TemplateDrawer";
|
||||||
import { PcPhotoStoryModal } from "./PcPhotoStoryModal";
|
import { PcPhotoStoryModal } from "./PcPhotoStoryModal";
|
||||||
import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer";
|
import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer";
|
||||||
|
import { AspectRatioSelector } from "./AspectRatioSelector";
|
||||||
|
|
||||||
const LauguageOptions = [
|
const LauguageOptions = [
|
||||||
{ value: "english", label: "English", isVip: false, code:'EN' },
|
{ value: "english", label: "English", isVip: false, code:'EN' },
|
||||||
@ -75,6 +76,8 @@ const VideoDurationOptions = [
|
|||||||
{ value: "unlimited", label: "unlimited" },
|
{ value: "unlimited", label: "unlimited" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// aspect ratio options moved to reusable component
|
||||||
|
|
||||||
/**模板故事模式弹窗组件 */
|
/**模板故事模式弹窗组件 */
|
||||||
/**
|
/**
|
||||||
* 防抖函数
|
* 防抖函数
|
||||||
@ -128,6 +131,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
language: string;
|
language: string;
|
||||||
videoDuration: string;
|
videoDuration: string;
|
||||||
expansion_mode: boolean;
|
expansion_mode: boolean;
|
||||||
|
aspect_ratio: "VIDEO_ASPECT_RATIO_LANDSCAPE" | "VIDEO_ASPECT_RATIO_PORTRAIT";
|
||||||
};
|
};
|
||||||
|
|
||||||
const [configOptions, setConfigOptions] = useState<ConfigOptions>({
|
const [configOptions, setConfigOptions] = useState<ConfigOptions>({
|
||||||
@ -136,6 +140,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
language: "english",
|
language: "english",
|
||||||
videoDuration: "unlimited",
|
videoDuration: "unlimited",
|
||||||
expansion_mode: true,
|
expansion_mode: true,
|
||||||
|
aspect_ratio: "VIDEO_ASPECT_RATIO_LANDSCAPE",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从 localStorage 初始化配置
|
// 从 localStorage 初始化配置
|
||||||
@ -150,6 +155,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
language: parsed.language || "english",
|
language: parsed.language || "english",
|
||||||
videoDuration: parsed.videoDuration || "1min",
|
videoDuration: parsed.videoDuration || "1min",
|
||||||
expansion_mode: typeof parsed.expansion_mode === 'boolean' ? parsed.expansion_mode : false,
|
expansion_mode: typeof parsed.expansion_mode === 'boolean' ? parsed.expansion_mode : false,
|
||||||
|
aspect_ratio: parsed.aspect_ratio || "VIDEO_ASPECT_RATIO_LANDSCAPE",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('解析保存的配置失败,使用默认配置:', error);
|
console.warn('解析保存的配置失败,使用默认配置:', error);
|
||||||
@ -205,6 +211,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
language: configOptions.language,
|
language: configOptions.language,
|
||||||
video_duration: configOptions.videoDuration,
|
video_duration: configOptions.videoDuration,
|
||||||
expansion_mode: configOptions.expansion_mode,
|
expansion_mode: configOptions.expansion_mode,
|
||||||
|
aspect_ratio: configOptions.aspect_ratio,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 调用创建剧集API
|
// 调用创建剧集API
|
||||||
@ -301,7 +308,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
{/* 第二行:功能按钮和Action按钮 - 同一行 */}
|
{/* 第二行:功能按钮和Action按钮 - 同一行 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
{/* 左侧功能按钮区域 */}
|
{/* 左侧功能按钮区域 */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1 flex-wrap sm:flex-nowrap">
|
||||||
{/* 获取创意按钮
|
{/* 获取创意按钮
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Get creative ideas for your story"
|
title="Get creative ideas for your story"
|
||||||
@ -335,7 +342,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 分隔线 */}
|
{/* 分隔线 */}
|
||||||
<div className="w-px h-4 bg-white/[0.20]"></div>
|
<div className="hidden sm:block w-px h-4 bg-white/[0.20]"></div>
|
||||||
|
|
||||||
{/* 图片故事按钮 */}
|
{/* 图片故事按钮 */}
|
||||||
<Tooltip title="Create movie from image" placement="top" trigger={isDesktop ? "hover" : "contextMenu"}>
|
<Tooltip title="Create movie from image" placement="top" trigger={isDesktop ? "hover" : "contextMenu"}>
|
||||||
@ -372,7 +379,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 分隔线 */}
|
{/* 分隔线 */}
|
||||||
<div className="w-px h-4 bg-white/[0.20]"></div>
|
<div className="hidden sm:block w-px h-4 bg-white/[0.20]"></div>
|
||||||
|
|
||||||
{/* 语言配置 */}
|
{/* 语言配置 */}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -400,7 +407,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
data-alt={`config-language`}
|
data-alt={`config-language`}
|
||||||
className={`flex items-center gap-1 text-white/80 transition-all duration-200 px-2 py-2`}
|
className={`flex items-center gap-1 text-white/80 transition-all duration-200 ${isMobile ? 'px-1' : 'px-2'} py-2`}
|
||||||
>
|
>
|
||||||
<Globe className={"w-4 h-4"} />
|
<Globe className={"w-4 h-4"} />
|
||||||
<span className="text-sm">{LauguageOptions.find((option) => option.value === configOptions.language)?.code}</span>
|
<span className="text-sm">{LauguageOptions.find((option) => option.value === configOptions.language)?.code}</span>
|
||||||
@ -408,7 +415,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
{/* 分隔线 */}
|
{/* 分隔线 */}
|
||||||
<div className="w-px h-4 bg-white/[0.20]"></div>
|
<div className="hidden sm:block w-px h-4 bg-white/[0.20]"></div>
|
||||||
|
|
||||||
{/* 剧本扩展开关 */}
|
{/* 剧本扩展开关 */}
|
||||||
<Tooltip title="Enable script expansion" placement="top" trigger={isDesktop ? "hover" : "click"}>
|
<Tooltip title="Enable script expansion" placement="top" trigger={isDesktop ? "hover" : "click"}>
|
||||||
@ -422,14 +429,14 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
onChange={(checked) => onConfigChange('expansion_mode', checked)}
|
onChange={(checked) => onConfigChange('expansion_mode', checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-xs text-white`}>
|
<span className={`text-xs text-white hidden sm:inline`}>
|
||||||
{configOptions.expansion_mode ? 'On' : 'Off'}
|
{configOptions.expansion_mode ? 'On' : 'Off'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 分隔线 */}
|
{/* 分隔线 */}
|
||||||
<div className="w-px h-4 bg-white/[0.20]"></div>
|
<div className="hidden sm:block w-px h-4 bg-white/[0.20]"></div>
|
||||||
|
|
||||||
{/* 时长选择 */}
|
{/* 时长选择 */}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -454,12 +461,23 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
data-alt={`config-video-duration`}
|
data-alt={`config-video-duration`}
|
||||||
className={`flex items-center gap-1 text-white/80 transition-all duration-200 px-2 py-2`}
|
className={`flex items-center gap-1 text-white/80 transition-all duration-200 ${isMobile ? 'px-1' : 'px-2'} py-2`}
|
||||||
>
|
>
|
||||||
<Clock className={"w-4 h-4"} />
|
<Clock className={"w-4 h-4"} />
|
||||||
<span className="text-sm">{configOptions.videoDuration}</span>
|
<span className="text-sm">{isMobile ? (configOptions.videoDuration === 'unlimited' ? '∞' : configOptions.videoDuration.replace('min', 'm')) : configOptions.videoDuration}</span>
|
||||||
</button>
|
</button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
|
{/* 分隔线(移动端隐藏,避免拥挤) */}
|
||||||
|
<div className="hidden sm:block w-px h-4 bg-white/[0.20]"></div>
|
||||||
|
|
||||||
|
{/* 横/竖屏选择 */}
|
||||||
|
<AspectRatioSelector
|
||||||
|
value={configOptions.aspect_ratio}
|
||||||
|
onChange={(v) => onConfigChange('aspect_ratio', v)}
|
||||||
|
placement="top"
|
||||||
|
className={`${isMobile ? '!px-1' : ''}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧Action按钮 */}
|
{/* 右侧Action按钮 */}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Drawer, Popconfirm, Tooltip, Upload } from "antd";
|
import { Drawer, Popconfirm, Tooltip, Upload, Dropdown } from "antd";
|
||||||
import { ImagePlay, Sparkles, Trash2 } from "lucide-react";
|
import { ImagePlay, Sparkles, Trash2 } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import GlobalLoad from "../common/GlobalLoad";
|
import GlobalLoad from "../common/GlobalLoad";
|
||||||
@ -9,6 +9,7 @@ import { ActionButton } from "../common/ActionButton";
|
|||||||
import { HighlightEditor } from "../common/HighlightEditor";
|
import { HighlightEditor } from "../common/HighlightEditor";
|
||||||
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
|
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
|
||||||
import { useLoadScriptText } from "@/app/service/domain/service";
|
import { useLoadScriptText } from "@/app/service/domain/service";
|
||||||
|
import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector";
|
||||||
|
|
||||||
type ConfigOptions = {
|
type ConfigOptions = {
|
||||||
mode: "auto" | "manual";
|
mode: "auto" | "manual";
|
||||||
@ -79,6 +80,7 @@ export const H5PhotoStoryDrawer = ({
|
|||||||
|
|
||||||
const { loadingText } = useLoadScriptText(isLoading);
|
const { loadingText } = useLoadScriptText(isLoading);
|
||||||
const [localLoading, setLocalLoading] = useState(0);
|
const [localLoading, setLocalLoading] = useState(0);
|
||||||
|
const [aspectUI, setAspectUI] = useState<AspectRatioValue>("VIDEO_ASPECT_RATIO_LANDSCAPE");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const taskProgressRef = useRef(taskProgress);
|
const taskProgressRef = useRef(taskProgress);
|
||||||
const [cursorPosition, setCursorPosition] = useState(0);
|
const [cursorPosition, setCursorPosition] = useState(0);
|
||||||
@ -117,7 +119,8 @@ export const H5PhotoStoryDrawer = ({
|
|||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode as "auto" | "manual",
|
configOptions.mode as "auto" | "manual",
|
||||||
configOptions.resolution as "720p" | "1080p" | "4k",
|
configOptions.resolution as "720p" | "1080p" | "4k",
|
||||||
configOptions.language
|
configOptions.language,
|
||||||
|
aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'
|
||||||
);
|
);
|
||||||
if (!episodeResponse) return;
|
if (!episodeResponse) return;
|
||||||
const episodeId = episodeResponse.project_id;
|
const episodeId = episodeResponse.project_id;
|
||||||
@ -355,7 +358,13 @@ export const H5PhotoStoryDrawer = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-alt="bottom-action-bar" className="sticky bottom-0 left-0 right-0 backdrop-blur border-t border-white/10 px-3 py-2">
|
<div data-alt="bottom-action-bar" className="sticky bottom-0 left-0 right-0 backdrop-blur border-t border-white/10 px-3 py-2">
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
{/* 横/竖屏选择 */}
|
||||||
|
<AspectRatioSelector
|
||||||
|
value={aspectUI}
|
||||||
|
onChange={setAspectUI}
|
||||||
|
placement="top"
|
||||||
|
/>
|
||||||
{!hasAnalyzed ? (
|
{!hasAnalyzed ? (
|
||||||
<Tooltip title={activeImageUrl ? "Analyze image content" : "Please upload an image first"} placement="top">
|
<Tooltip title={activeImageUrl ? "Analyze image content" : "Please upload an image first"} placement="top">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { useUploadFile } from "@/app/service/domain/service";
|
|||||||
import { ActionButton } from "../common/ActionButton";
|
import { ActionButton } from "../common/ActionButton";
|
||||||
import GlobalLoad from "../common/GlobalLoad";
|
import GlobalLoad from "../common/GlobalLoad";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { Dropdown } from "antd";
|
||||||
|
import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector";
|
||||||
|
|
||||||
interface H5TemplateDrawerProps {
|
interface H5TemplateDrawerProps {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
@ -73,6 +75,7 @@ export const H5TemplateDrawer = ({
|
|||||||
const [isDescExpanded, setIsDescExpanded] = useState(false);
|
const [isDescExpanded, setIsDescExpanded] = useState(false);
|
||||||
// 自由输入框布局
|
// 自由输入框布局
|
||||||
const [freeInputLayout, setFreeInputLayout] = useState('bottom');
|
const [freeInputLayout, setFreeInputLayout] = useState('bottom');
|
||||||
|
const [aspectUI, setAspectUI] = useState<AspectRatioValue>("VIDEO_ASPECT_RATIO_LANDSCAPE");
|
||||||
|
|
||||||
// 自由输入框布局
|
// 自由输入框布局
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -119,7 +122,8 @@ export const H5TemplateDrawer = ({
|
|||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode,
|
configOptions.mode,
|
||||||
configOptions.resolution,
|
configOptions.resolution,
|
||||||
configOptions.language
|
configOptions.language,
|
||||||
|
aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
@ -552,6 +556,12 @@ export const H5TemplateDrawer = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 横/竖屏选择 */}
|
||||||
|
<AspectRatioSelector
|
||||||
|
value={aspectUI}
|
||||||
|
onChange={setAspectUI}
|
||||||
|
placement="top"
|
||||||
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={isTemplateCreating || localLoading > 0}
|
isCreating={isTemplateCreating || localLoading > 0}
|
||||||
handleCreateVideo={handleConfirm}
|
handleCreateVideo={handleConfirm}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Modal, Tooltip, Popconfirm, Upload } from "antd";
|
import { Modal, Tooltip, Popconfirm, Upload, Dropdown } from "antd";
|
||||||
import { ImagePlay, Sparkles, Trash2 } from "lucide-react";
|
import { ImagePlay, Sparkles, Trash2 } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import GlobalLoad from "../common/GlobalLoad";
|
import GlobalLoad from "../common/GlobalLoad";
|
||||||
@ -9,6 +9,7 @@ import { ActionButton } from "../common/ActionButton";
|
|||||||
import { HighlightEditor } from "../common/HighlightEditor";
|
import { HighlightEditor } from "../common/HighlightEditor";
|
||||||
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
|
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
|
||||||
import { useLoadScriptText } from "@/app/service/domain/service";
|
import { useLoadScriptText } from "@/app/service/domain/service";
|
||||||
|
import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector";
|
||||||
|
|
||||||
type ConfigOptions = {
|
type ConfigOptions = {
|
||||||
mode: "auto" | "manual";
|
mode: "auto" | "manual";
|
||||||
@ -61,6 +62,7 @@ export const PcPhotoStoryModal = ({
|
|||||||
} = useImageStoryServiceHook();
|
} = useImageStoryServiceHook();
|
||||||
const { loadingText } = useLoadScriptText(isLoading);
|
const { loadingText } = useLoadScriptText(isLoading);
|
||||||
const [localLoading, setLocalLoading] = useState(0);
|
const [localLoading, setLocalLoading] = useState(0);
|
||||||
|
const [aspectUI, setAspectUI] = useState<AspectRatioValue>("VIDEO_ASPECT_RATIO_LANDSCAPE");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const taskProgressRef = useRef(taskProgress);
|
const taskProgressRef = useRef(taskProgress);
|
||||||
const [cursorPosition, setCursorPosition] = useState(0);
|
const [cursorPosition, setCursorPosition] = useState(0);
|
||||||
@ -103,7 +105,8 @@ export const PcPhotoStoryModal = ({
|
|||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode as "auto" | "manual",
|
configOptions.mode as "auto" | "manual",
|
||||||
configOptions.resolution as "720p" | "1080p" | "4k",
|
configOptions.resolution as "720p" | "1080p" | "4k",
|
||||||
configOptions.language
|
configOptions.language,
|
||||||
|
aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'
|
||||||
);
|
);
|
||||||
if (!episodeResponse) return;
|
if (!episodeResponse) return;
|
||||||
const episodeId = episodeResponse.project_id;
|
const episodeId = episodeResponse.project_id;
|
||||||
@ -317,7 +320,13 @@ export const PcPhotoStoryModal = ({
|
|||||||
type={"role"}
|
type={"role"}
|
||||||
placeholder="Share your creative ideas about the image and let AI create a movie story for you..."
|
placeholder="Share your creative ideas about the image and let AI create a movie story for you..."
|
||||||
/>
|
/>
|
||||||
<div className="absolute bottom-1 right-0 flex gap-2">
|
<div className="absolute bottom-1 right-0 flex gap-2 items-center">
|
||||||
|
{/* 横/竖屏选择 */}
|
||||||
|
<AspectRatioSelector
|
||||||
|
value={aspectUI}
|
||||||
|
onChange={setAspectUI}
|
||||||
|
placement="top"
|
||||||
|
/>
|
||||||
{!hasAnalyzed ? (
|
{!hasAnalyzed ? (
|
||||||
<Tooltip title={activeImageUrl ? "Analyze image content" : "Please upload an image first"} placement="top">
|
<Tooltip title={activeImageUrl ? "Analyze image content" : "Please upload an image first"} placement="top">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Upload,
|
Upload,
|
||||||
Image,
|
Image,
|
||||||
|
Dropdown,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { UploadOutlined } from "@ant-design/icons";
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
import { StoryTemplateEntity } from "@/app/service/domain/Entities";
|
import { StoryTemplateEntity } from "@/app/service/domain/Entities";
|
||||||
@ -20,6 +21,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { useUploadFile } from "@/app/service/domain/service";
|
import { useUploadFile } from "@/app/service/domain/service";
|
||||||
import { ActionButton } from "../common/ActionButton";
|
import { ActionButton } from "../common/ActionButton";
|
||||||
import GlobalLoad from "../common/GlobalLoad";
|
import GlobalLoad from "../common/GlobalLoad";
|
||||||
|
import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防抖函数
|
* 防抖函数
|
||||||
@ -102,6 +104,7 @@ export const PcTemplateModal = ({
|
|||||||
// 自由输入框布局
|
// 自由输入框布局
|
||||||
const [freeInputLayout, setFreeInputLayout] = useState('bottom');
|
const [freeInputLayout, setFreeInputLayout] = useState('bottom');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [aspectUI, setAspectUI] = useState<AspectRatioValue>("VIDEO_ASPECT_RATIO_LANDSCAPE");
|
||||||
|
|
||||||
// 组件挂载时获取模板列表
|
// 组件挂载时获取模板列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -179,7 +182,8 @@ export const PcTemplateModal = ({
|
|||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode,
|
configOptions.mode,
|
||||||
configOptions.resolution,
|
configOptions.resolution,
|
||||||
configOptions.language
|
configOptions.language,
|
||||||
|
aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
@ -703,6 +707,12 @@ export const PcTemplateModal = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 横/竖屏选择 */}
|
||||||
|
<AspectRatioSelector
|
||||||
|
value={aspectUI}
|
||||||
|
onChange={setAspectUI}
|
||||||
|
placement="top"
|
||||||
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={isTemplateCreating || localLoading > 0}
|
isCreating={isTemplateCreating || localLoading > 0}
|
||||||
handleCreateVideo={handleConfirm}
|
handleCreateVideo={handleConfirm}
|
||||||
|
|||||||
4
components/ChatInputBox/types.ts
Normal file
4
components/ChatInputBox/types.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const AspectRatioOptions = [
|
||||||
|
{ value: "VIDEO_ASPECT_RATIO_LANDSCAPE", label: "16:9" },
|
||||||
|
{ value: "VIDEO_ASPECT_RATIO_PORTRAIT", label: "9:16" },
|
||||||
|
];
|
||||||
Loading…
x
Reference in New Issue
Block a user