From 170a8b7a4f3d3d820acf6bf314bb127af58286f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Sat, 20 Sep 2025 16:33:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E8=A7=86=E9=A2=91=E6=AF=94?= =?UTF-8?q?=E4=BE=8B=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/DTO/movie_start_dto.ts | 4 + app/service/Interaction/ImageStoryService.ts | 9 +- .../Interaction/templateStoryService.ts | 9 +- .../ChatInputBox/AspectRatioSelector.tsx | 86 +++++++++++++++++++ components/ChatInputBox/ChatInputBox.tsx | 36 ++++++-- .../ChatInputBox/H5PhotoStoryDrawer.tsx | 15 +++- components/ChatInputBox/H5TemplateDrawer.tsx | 12 ++- components/ChatInputBox/PcPhotoStoryModal.tsx | 15 +++- components/ChatInputBox/PcTemplateModal.tsx | 12 ++- components/ChatInputBox/types.ts | 4 + 10 files changed, 179 insertions(+), 23 deletions(-) create mode 100644 components/ChatInputBox/AspectRatioSelector.tsx create mode 100644 components/ChatInputBox/types.ts diff --git a/api/DTO/movie_start_dto.ts b/api/DTO/movie_start_dto.ts index 1d18456..5676d18 100644 --- a/api/DTO/movie_start_dto.ts +++ b/api/DTO/movie_start_dto.ts @@ -106,6 +106,8 @@ export interface CreateMovieProjectV2Request { language: string; /** 图片URL */ image_url: string; + /** 画面比例(横/竖屏) */ + aspect_ratio?: "16:9" | "9:16"; } /** @@ -249,6 +251,8 @@ export interface CreateMovieProjectV3Request { /** 道具照片URL */ photo_url: string; }[]; + /** 画面比例(横/竖屏) */ + aspect_ratio?: "16:9" | "9:16"; } /** diff --git a/app/service/Interaction/ImageStoryService.ts b/app/service/Interaction/ImageStoryService.ts index 7ec2a46..ee7b6f8 100644 --- a/app/service/Interaction/ImageStoryService.ts +++ b/app/service/Interaction/ImageStoryService.ts @@ -57,7 +57,8 @@ interface UseImageStoryService { user_id: string, mode?: "auto" | "manual", resolution?: "720p" | "1080p" | "4k", - language?: string + language?: string, + aspectRatio?: "16:9" | "9:16" ) => Promise<{ project_id: string } | undefined>; /** 设置角色分析 */ setCharactersAnalysis: Dispatch>; @@ -457,7 +458,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { user_id: string, mode: "auto" | "manual" = "auto", resolution: "720p" | "1080p" | "4k" = "720p", - language: string = "English" + language: string = "English", + aspectRatio?: "16:9" | "9:16" ) => { try { if (hasAnalyzed) { @@ -480,7 +482,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { character_briefs, language, image_url: activeImageUrl, - project_id:taskId + project_id:taskId, + ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}) }; // 调用create_movie_project_v2接口 diff --git a/app/service/Interaction/templateStoryService.ts b/app/service/Interaction/templateStoryService.ts index 9b67286..d45f2a2 100644 --- a/app/service/Interaction/templateStoryService.ts +++ b/app/service/Interaction/templateStoryService.ts @@ -28,7 +28,8 @@ interface UseTemplateStoryService { user_id: string, mode: "auto" | "manual", resolution: "720p" | "1080p" | "4k", - language: string + language: string, + aspectRatio?: "16:9" | "9:16" ) => Promise; /** 设置选中的模板 */ setSelectedTemplate: (template: StoryTemplateEntity | null) => void; @@ -250,7 +251,8 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { user_id: string, mode: "auto" | "manual" = "auto", resolution: "720p" | "1080p" | "4k" = "720p", - language: string = "English" + language: string = "English", + aspectRatio?: "16:9" | "9:16" ) => { console.log('selectedTemplate', selectedTemplate) try { @@ -271,7 +273,8 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { storyItem: selectedTemplate?.storyItem || [], freeInput: selectedTemplate?.freeInput || [], language, - template_id: selectedTemplate?.template_id || "" + template_id: selectedTemplate?.template_id || "", + ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}) }; console.log("params", params); const result = await MovieProjectService.createProject( diff --git a/components/ChatInputBox/AspectRatioSelector.tsx b/components/ChatInputBox/AspectRatioSelector.tsx new file mode 100644 index 0000000..97ed5f5 --- /dev/null +++ b/components/ChatInputBox/AspectRatioSelector.tsx @@ -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 ( + ({ + key: option.value, + label: ( +
+ {option.value === "VIDEO_ASPECT_RATIO_LANDSCAPE" ? ( + + ) : ( + + )} + {option.label} +
+ ), + })), + onClick: ({ key }) => onChange(key as AspectRatioValue), + }} + trigger={["click"]} + placement={placement} + > + +
+ ); +}; + +export default AspectRatioSelector; + + diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index f79f976..c1a98db 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -50,6 +50,7 @@ import { PcTemplateModal } from "./PcTemplateModal"; import { H5TemplateDrawer } from "./H5TemplateDrawer"; import { PcPhotoStoryModal } from "./PcPhotoStoryModal"; import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer"; +import { AspectRatioSelector } from "./AspectRatioSelector"; const LauguageOptions = [ { value: "english", label: "English", isVip: false, code:'EN' }, @@ -75,6 +76,8 @@ const VideoDurationOptions = [ { value: "unlimited", label: "unlimited" }, ]; +// aspect ratio options moved to reusable component + /**模板故事模式弹窗组件 */ /** * 防抖函数 @@ -128,6 +131,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { language: string; videoDuration: string; expansion_mode: boolean; + aspect_ratio: "VIDEO_ASPECT_RATIO_LANDSCAPE" | "VIDEO_ASPECT_RATIO_PORTRAIT"; }; const [configOptions, setConfigOptions] = useState({ @@ -136,6 +140,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { language: "english", videoDuration: "unlimited", expansion_mode: true, + aspect_ratio: "VIDEO_ASPECT_RATIO_LANDSCAPE", }); // 从 localStorage 初始化配置 @@ -150,6 +155,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { language: parsed.language || "english", videoDuration: parsed.videoDuration || "1min", expansion_mode: typeof parsed.expansion_mode === 'boolean' ? parsed.expansion_mode : false, + aspect_ratio: parsed.aspect_ratio || "VIDEO_ASPECT_RATIO_LANDSCAPE", }); } catch (error) { console.warn('解析保存的配置失败,使用默认配置:', error); @@ -205,6 +211,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { language: configOptions.language, video_duration: configOptions.videoDuration, expansion_mode: configOptions.expansion_mode, + aspect_ratio: configOptions.aspect_ratio, }; // 调用创建剧集API @@ -301,7 +308,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { {/* 第二行:功能按钮和Action按钮 - 同一行 */}
{/* 左侧功能按钮区域 */} -
+
{/* 获取创意按钮 {/* 分隔线 */} -
+
{/* 图片故事按钮 */} @@ -372,7 +379,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { )} {/* 分隔线 */} -
+
{/* 语言配置 */}
- +
{/* 分隔线 */} -
+
{/* 时长选择 */} + + {/* 分隔线(移动端隐藏,避免拥挤) */} +
+ + {/* 横/竖屏选择 */} + onConfigChange('aspect_ratio', v)} + placement="top" + className={`${isMobile ? '!px-1' : ''}`} + />
{/* 右侧Action按钮 */} diff --git a/components/ChatInputBox/H5PhotoStoryDrawer.tsx b/components/ChatInputBox/H5PhotoStoryDrawer.tsx index c41fd10..562e1b1 100644 --- a/components/ChatInputBox/H5PhotoStoryDrawer.tsx +++ b/components/ChatInputBox/H5PhotoStoryDrawer.tsx @@ -1,7 +1,7 @@ "use client"; 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 { useRouter } from "next/navigation"; import GlobalLoad from "../common/GlobalLoad"; @@ -9,6 +9,7 @@ import { ActionButton } from "../common/ActionButton"; import { HighlightEditor } from "../common/HighlightEditor"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; import { useLoadScriptText } from "@/app/service/domain/service"; +import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector"; type ConfigOptions = { mode: "auto" | "manual"; @@ -79,6 +80,7 @@ export const H5PhotoStoryDrawer = ({ const { loadingText } = useLoadScriptText(isLoading); const [localLoading, setLocalLoading] = useState(0); + const [aspectUI, setAspectUI] = useState("VIDEO_ASPECT_RATIO_LANDSCAPE"); const router = useRouter(); const taskProgressRef = useRef(taskProgress); const [cursorPosition, setCursorPosition] = useState(0); @@ -117,7 +119,8 @@ export const H5PhotoStoryDrawer = ({ String(User.id), configOptions.mode as "auto" | "manual", configOptions.resolution as "720p" | "1080p" | "4k", - configOptions.language + configOptions.language, + aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16' ); if (!episodeResponse) return; const episodeId = episodeResponse.project_id; @@ -355,7 +358,13 @@ export const H5PhotoStoryDrawer = ({
-
+
+ {/* 横/竖屏选择 */} + {!hasAnalyzed ? (
diff --git a/components/ChatInputBox/H5TemplateDrawer.tsx b/components/ChatInputBox/H5TemplateDrawer.tsx index a8a803b..bbc6116 100644 --- a/components/ChatInputBox/H5TemplateDrawer.tsx +++ b/components/ChatInputBox/H5TemplateDrawer.tsx @@ -11,6 +11,8 @@ import { useUploadFile } from "@/app/service/domain/service"; import { ActionButton } from "../common/ActionButton"; import GlobalLoad from "../common/GlobalLoad"; import { motion, AnimatePresence } from "framer-motion"; +import { Dropdown } from "antd"; +import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector"; interface H5TemplateDrawerProps { isMobile: boolean; @@ -73,6 +75,7 @@ export const H5TemplateDrawer = ({ const [isDescExpanded, setIsDescExpanded] = useState(false); // 自由输入框布局 const [freeInputLayout, setFreeInputLayout] = useState('bottom'); + const [aspectUI, setAspectUI] = useState("VIDEO_ASPECT_RATIO_LANDSCAPE"); // 自由输入框布局 useEffect(() => { @@ -119,7 +122,8 @@ export const H5TemplateDrawer = ({ String(User.id), configOptions.mode, configOptions.resolution, - configOptions.language + configOptions.language, + aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16' ); if (projectId) { @@ -552,6 +556,12 @@ export const H5TemplateDrawer = ({ />
)} + {/* 横/竖屏选择 */} + 0} handleCreateVideo={handleConfirm} diff --git a/components/ChatInputBox/PcPhotoStoryModal.tsx b/components/ChatInputBox/PcPhotoStoryModal.tsx index cea4cd2..e03fe0c 100644 --- a/components/ChatInputBox/PcPhotoStoryModal.tsx +++ b/components/ChatInputBox/PcPhotoStoryModal.tsx @@ -1,7 +1,7 @@ "use client"; 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 { useRouter } from "next/navigation"; import GlobalLoad from "../common/GlobalLoad"; @@ -9,6 +9,7 @@ import { ActionButton } from "../common/ActionButton"; import { HighlightEditor } from "../common/HighlightEditor"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; import { useLoadScriptText } from "@/app/service/domain/service"; +import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector"; type ConfigOptions = { mode: "auto" | "manual"; @@ -61,6 +62,7 @@ export const PcPhotoStoryModal = ({ } = useImageStoryServiceHook(); const { loadingText } = useLoadScriptText(isLoading); const [localLoading, setLocalLoading] = useState(0); + const [aspectUI, setAspectUI] = useState("VIDEO_ASPECT_RATIO_LANDSCAPE"); const router = useRouter(); const taskProgressRef = useRef(taskProgress); const [cursorPosition, setCursorPosition] = useState(0); @@ -103,7 +105,8 @@ export const PcPhotoStoryModal = ({ String(User.id), configOptions.mode as "auto" | "manual", configOptions.resolution as "720p" | "1080p" | "4k", - configOptions.language + configOptions.language, + aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16' ); if (!episodeResponse) return; const episodeId = episodeResponse.project_id; @@ -317,7 +320,13 @@ export const PcPhotoStoryModal = ({ type={"role"} placeholder="Share your creative ideas about the image and let AI create a movie story for you..." /> -
+
+ {/* 横/竖屏选择 */} + {!hasAnalyzed ? ( ("VIDEO_ASPECT_RATIO_LANDSCAPE"); // 组件挂载时获取模板列表 useEffect(() => { @@ -179,7 +182,8 @@ export const PcTemplateModal = ({ String(User.id), configOptions.mode, configOptions.resolution, - configOptions.language + configOptions.language, + aspectUI === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16' ); if (projectId) { @@ -703,6 +707,12 @@ export const PcTemplateModal = ({ />
)} + {/* 横/竖屏选择 */} + 0} handleCreateVideo={handleConfirm} diff --git a/components/ChatInputBox/types.ts b/components/ChatInputBox/types.ts new file mode 100644 index 0000000..f442962 --- /dev/null +++ b/components/ChatInputBox/types.ts @@ -0,0 +1,4 @@ +export const AspectRatioOptions = [ + { value: "VIDEO_ASPECT_RATIO_LANDSCAPE", label: "16:9" }, + { value: "VIDEO_ASPECT_RATIO_PORTRAIT", label: "9:16" }, +]; \ No newline at end of file