diff --git a/.env.development b/.env.development index e9c8845..58dc325 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com +# NEXT_PUBLIC_BASE_URL = https://pre.movieflow.api.huiying.video NEXT_PUBLIC_API_BASE_URL = https://77.api.qikongjian.com diff --git a/.env.production b/.env.production index 8cbf1fd..d3ac252 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,4 @@ -# NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com -NEXT_PUBLIC_BASE_URL = https://pre.movieflow.api.huiying.video +NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com +# NEXT_PUBLIC_BASE_URL = https://pre.movieflow.api.huiying.video NEXT_PUBLIC_API_BASE_URL = https://77.api.qikongjian.com NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com diff --git a/api/DTO/movieEdit.ts b/api/DTO/movieEdit.ts index 0d92cca..28745c0 100644 --- a/api/DTO/movieEdit.ts +++ b/api/DTO/movieEdit.ts @@ -619,6 +619,10 @@ export interface RoleResponse { /**缓存 */ character_draft: string; } +export interface RealRoleResponse { + system_characters: []; + project_characters: RoleResponse[]; +} export interface Role { diff --git a/api/request.ts b/api/request.ts index e962187..7a59dbf 100644 --- a/api/request.ts +++ b/api/request.ts @@ -50,6 +50,7 @@ request.interceptors.response.use( (response: AxiosResponse) => { // 检查业务状态码 if (response.data?.code !== 0 && response.data?.code !== 202) { + console.log('response', response) // 处理业务层面的错误 const businessCode = response.data?.code; const errorMessage = response.data?.message; @@ -64,7 +65,6 @@ request.interceptors.response.use( errorHandle(4001, errorMessage); return Promise.reject(new Error(errorMessage)); } - // 其他业务错误 errorHandle(0, errorMessage); return Promise.reject(new Error(errorMessage)); diff --git a/api/video_flow.ts b/api/video_flow.ts index ff82f46..43bdf45 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -16,7 +16,7 @@ import { } from "@/app/service/domain/valueObject"; import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./DTO/movieEdit"; -import { RoleResponse } from "./DTO/movieEdit"; +import { RealRoleResponse } from "./DTO/movieEdit"; import { RoleRecognitionResponse } from "./DTO/movieEdit"; /** @@ -665,6 +665,16 @@ export const regenerateShot = async (request: { return post("/movie/regenerate_shot_video", request); }; +// 重新生成视频 +export const regenerateVideo = async (request: { + /** 项目ID */ + project_id: string; + /** 视频ID */ + video_id: string; +}): Promise> => { + return post("/movie_cut/regenerate_video", request); +}; + /** * 获取分镜列表 @@ -1165,14 +1175,14 @@ export const getSimilarCharacters = async (request: { /** * 获取项目角色列表(含高亮关键词)接口 * @param request - 项目角色列表请求参数 - * @returns Promise> 项目角色列表 + * @returns Promise> 项目角色列表 */ export const getCharacterListByProjectWithHighlight = async (request: { /** 项目ID */ project_id: string; /** 每个角色最多提取的高亮关键词数量 */ max_keywords?: number; -}): Promise> => { +}): Promise> => { return post("/character/list_by_project_with_highlight", request); }; diff --git a/app/activate/page.tsx b/app/activate/page.tsx index 5fdc04d..d342139 100644 --- a/app/activate/page.tsx +++ b/app/activate/page.tsx @@ -6,7 +6,7 @@ import { CheckCircle, XCircle, Loader2 } from "lucide-react"; export default function Activate() { const searchParams = useSearchParams(); - const t = searchParams.get("t") || ''; + const t = searchParams.get("t") || ""; const type = searchParams.get("type"); if (type === "confirm_email") { @@ -21,32 +21,40 @@ export default function Activate() { * @param {string} t - Verification token */ function ConfirmEmail({ t }: { t: string }) { - const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); - const [message, setMessage] = useState(''); + const [status, setStatus] = useState<"loading" | "success" | "error">( + "loading" + ); + const [message, setMessage] = useState(""); useEffect(() => { if (!t) { - setStatus('error'); - setMessage('Invalid verification token'); + setStatus("error"); + setMessage("Invalid verification token"); return; } - - post(`${process.env.NEXT_PUBLIC_JAVA_URL}/api/user/activate?t=${t}`) - .then((res) => { - setStatus('success'); - setMessage('Your registration has been verified. Please return to the official website to log in.'); - }) - .catch((err) => { - setStatus('error'); - setMessage('Verification failed. Please try again.'); - }); + post(`/auth/activate`, { + t: t, + }).then((res:any) => { + console.log('res', res) + setStatus("success"); + setMessage( + "Your registration has been verified. Please return to the official website to log in." + ); + }).catch((err:any) => { + console.log('err', err) + setStatus("error"); + setMessage("Verification failed. Please try again."); + }); }, [t]); const renderContent = () => { switch (status) { - case 'loading': + case "loading": return ( -
+
@@ -55,52 +63,72 @@ function ConfirmEmail({ t }: { t: string }) {
); - case 'success': + case "success": return ( -
+
-

Verification Successful

+

+ Verification Successful +

{message}

); - case 'error': + case "error": return ( -
+
-

Verification Failed

+

+ Verification Failed +

{message}

- + */}
); } }; - return ( -
-
+ return ( +
+
- {status === 'loading' && ( -
-

Email Verification

-

Please wait while we verify your email

-
- )} + {status === "loading" && ( +
+

+ Email Verification +

+

+ Please wait while we verify your email +

+
+ )} - {renderContent()} + {renderContent()}
diff --git a/app/layout.tsx b/app/layout.tsx index 232e151..1dd6e9c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -39,7 +39,7 @@ export default function RootLayout({ return ( - MovieFlow - AI Film Studio + MovieFlow - AI Movie Studio diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index a733491..0418f22 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -16,37 +16,6 @@ import { DashboardLayout } from "@/components/layout/dashboard-layout"; export default function PricingPage() { - useEffect(() => { - // 获取当前窗口尺寸 - const currentWidth = window.innerWidth; - const currentHeight = window.innerHeight; - // 计算缩放比例 (1920x1080) - const wScale = currentWidth / 1920; - const hScale = currentHeight / 1080; - - // 检查app节点是否存在 - const pricingPage = document.getElementById("pricing-page"); - if (!pricingPage) { - console.error("未找到app节点"); - return; - } - // setHPading((hScale || 1) * 10); - // 创建样式元素 - const style = document.createElement("style"); - - // 设置CSS样式 - style.textContent = ` - #pricing-page { - transform-origin: top left; - transform: scale(${wScale}, ${hScale}); - width: 1920px; - height: 1080px; - } - `; - - // 将样式添加到head - document.head.appendChild(style); - }, []); return (
@@ -135,95 +104,341 @@ function HomeModule5() { }; return (
+
-
-

Pick a plan and make it yours -

+ - {/* 计费切换 */} -
- - -
+ {/* 计费切换 */} +
+ +
- - {/* 主要价格卡片 */} -
- {pricingPlans.map((plan, index) => ( -
-

- {plan.title} -

-
- - ${plan.price} - - / {billingType === "month" ? "mo" : "year"} -
-

- {plan.credits} -

- {plan.issubscribed ? ( - - ) : ( - - )} -

- * Billed monthly until cancelled -

-
    - {plan.features.map((feature, featureIndex) => ( -
  • - - {feature} -
  • - ))} -
-
- ))} -
- -
+ + {/* 主要价格卡片 */} +
+ {pricingPlans.map((plan, index) => ( +
+

+ {plan.title} +

+
+ + ${plan.price} + + + / {billingType === "month" ? "mo" : "year"} + +
+

+ {plan.credits} +

+ {plan.issubscribed ? ( + + ) : ( + + )} +

+ * Billed monthly until cancelled +

+
    + {plan.features.map((feature, featureIndex) => ( +
  • + + ✓ + + {feature} +
  • + ))} +
+
+ ))} +
+
); } diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts index de33edf..890e2ce 100644 --- a/app/service/Interaction/RoleShotService.ts +++ b/app/service/Interaction/RoleShotService.ts @@ -188,7 +188,7 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity, shot_id: shot.id, // 单个分镜ID character_replacements: characterReplacements, wait_for_completion: false, // 不等待完成,异步处理 - character_draft: JSON.stringify(newDraftRoleList) + character_draft: newDraftRoleList ? JSON.stringify(newDraftRoleList) : "" }) })) diff --git a/app/service/Interaction/templateStoryService.ts b/app/service/Interaction/templateStoryService.ts index 1514009..99f8a13 100644 --- a/app/service/Interaction/templateStoryService.ts +++ b/app/service/Interaction/templateStoryService.ts @@ -80,6 +80,64 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { } }, [templateStoryUseCase]); + /** + * 更新指定角色的图片 + * @param {string} roleName - 角色名称 + * @param {string} imageUrl - 新的图片URL + */ + const updateRoleImage = useCallback( + (roleName: string, imageUrl: string): void => { + if (!selectedTemplate) { + console.warn("updateRoleImage: selectedTemplate 为空"); + return; + } + + console.log(`更新角色 ${roleName} 的图片:`, imageUrl); + + const updatedTemplate = { + ...selectedTemplate, + storyRole: selectedTemplate.storyRole.map((role) => + role.role_name === roleName + ? { ...role, photo_url: imageUrl } + : role + ), + }; + + console.log("更新后的模板:", updatedTemplate); + setSelectedTemplate(updatedTemplate); + }, + [selectedTemplate] + ); + + /** + * 更新指定道具的图片 + * @param {string} itemName - 道具名称 + * @param {string} imageUrl - 新的图片URL + */ + const updateItemImage = useCallback( + (itemName: string, imageUrl: string): void => { + if (!selectedTemplate) { + console.warn("updateItemImage: selectedTemplate 为空"); + return; + } + + console.log(`更新道具 ${itemName} 的图片:`, imageUrl); + + const updatedTemplate = { + ...selectedTemplate, + storyItem: selectedTemplate.storyItem.map((item) => + item.item_name === itemName + ? { ...item, photo_url: imageUrl } + : item + ), + }; + + console.log("更新后的模板:", updatedTemplate); + setSelectedTemplate(updatedTemplate); + }, + [selectedTemplate] + ); + /** * 上传人物头像并分析特征,替换旧的角色数据 * @param {string} imageUrl - 图片URL @@ -98,53 +156,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { setIsLoading(false); } }, - [] - ); - - /** - * 更新指定角色的图片 - * @param {string} roleName - 角色名称 - * @param {string} imageUrl - 新的图片URL - */ - const updateRoleImage = useCallback( - (roleName: string, imageUrl: string): void => { - if (!selectedTemplate) return; - - const updatedTemplate = { - ...selectedTemplate, - storyRole: selectedTemplate.storyRole.map((role) => - role.role_name === roleName - ? { ...role, photo_url: imageUrl } - : role - ), - }; - - setSelectedTemplate(updatedTemplate); - }, - [selectedTemplate] - ); - - /** - * 更新指定道具的图片 - * @param {string} itemName - 道具名称 - * @param {string} imageUrl - 新的图片URL - */ - const updateItemImage = useCallback( - (itemName: string, imageUrl: string): void => { - if (!selectedTemplate) return; - - const updatedTemplate = { - ...selectedTemplate, - storyItem: selectedTemplate.storyItem.map((item) => - item.item_name === itemName - ? { ...item, photo_url: imageUrl } - : item - ), - }; - - setSelectedTemplate(updatedTemplate); - }, - [selectedTemplate] + [updateRoleImage] ); /** diff --git a/app/service/uml/DDDLayer.md b/app/service/uml/DDDLayer.md index 25dc8ef..7d9a1bf 100644 --- a/app/service/uml/DDDLayer.md +++ b/app/service/uml/DDDLayer.md @@ -7,13 +7,11 @@ - React组件(Modal、Form、Button等) - 样式和布局逻辑 - 用户交互事件绑定 -- 通过props接收数据和回调函数 - +- 越是可复用组件,越要小心定义,越要简单,宁可页面中的组件使用多做点,也要组件少做点,因为复用越多,它就可能发生的变化越多,除非你很确定它的变化不会更多了。 **设计原则**: - 不包含业务逻辑 - 不直接操作状态 -- 通过回调函数与上层通信 -- 可复用和可测试 + ### 2. Hook (状态管理层) **职责**:状态管理、副作用处理、业务逻辑组合 @@ -26,11 +24,29 @@ **设计原则**: - 协调全局状态和本地状态 - 处理异步操作和副作用 -- 组合多个UseCase的调用 +- 组合封装UseCase的调用,让react的方便性和hook思想体现出来 - 提供响应式的数据接口 ### 3. UseCase (业务逻辑层) -**职责**:核心业务规则、业务流程编排、领域逻辑验证 +**职责**:核心业务规则、业务流程编排、领域逻辑验证,它里面的流程,应该一眼看过去,瞬间就知道这个用例中包含的业务是干啥的,里面应该是去调用一个个service,并且有注释说明,它是会最经常发生更改的,所以它要足够简单,要足够注释。 +```ts +class DemoUseCase { + constructor(private readonly aService: AService, private readonly bService: BService,cStore:Store) {} + async execute(href:string,storedate:any) { + // 获取a数据 + const aData = await this.aService.getA(href); + // 处理b请求 + const b = await this.bService.getB(aData); + // 更新cStore + this.cStore.update({ + ...storedate, + b, + }); + + return b; + } +} +``` **内容**: - 业务用例类(ImageStoryUseCase、ScriptGenerationUseCase等) - 完整的业务流程方法 @@ -43,11 +59,11 @@ - 协调多个Service的调用 - 返回领域实体,而不是原始数据 - 无状态,方法调用间不依赖实例状态 +- 无关任何react的东西,纯粹的TS代码 ### 4. Service (外部服务层) -**职责**:外部服务集成、技术实现细节、基础设施 +**职责**:外部服务集成、技术实现细节、基础设施,它应该是一堆的服务函数,纯函数,输入到输出,没有外部的闭包依赖。不要觉得有时候可以从其它地方直接依赖,就不用参数来接收,它要干净,要是纯函数,要能通过参数判断其全部所需,所以要用接收参数的方式 **内容**: -- 外部API调用服务(ImageProcessingService、AIAnalysisService等) - 技术实现细节(图片处理、网络请求等) - 错误处理和重试逻辑 - 可以被多个UseCase复用的服务 @@ -57,58 +73,41 @@ - 处理外部API调用 - 提供统一的错误处理 - 实现接口隔离原则 +- 无关任何react的东西,纯粹的TS代码 ### 5. Repository (数据访问层) -**职责**:数据持久化、数据查询、数据转换 -**内容**: -- 数据访问类(ImageStoryRepository、CharacterRepository等) -- 数据持久化逻辑 -- 数据查询和过滤 -- 数据转换(Entity ↔ Data) - -**设计原则**: -- 封装数据访问细节 -- 提供统一的CRUD接口 -- 处理数据转换 -- 实现数据访问的抽象 +使用ApiFox去对接好后端的接口文档,然后MCP接入cursor,从而直接生成接口请求函数以及类型,相对于没有这个东西,cursor帮你写的代码将是天差地别的,能提高你大约20%的开发效率。当然这要协同后端好好写接口参数和响应的内容。 ## 📊 状态管理策略 -**状态分类与存储位置**: -- **业务核心状态** → Zustand Store(currentStory、storyList等) -- **UI交互状态** → 组件useState(isModalOpen、localInput等) -- **业务流程状态** → UseCase私有状态(analysisProgress等) -- **用户偏好状态** → Zustand Store(selectedCategory、theme等) -- **临时计算状态** → useMemo/useCallback(filteredStories等) +**状态分类**: +- **功能业务模块状态** → 微型Store,用不同模块功能去拆Store 而非页面,记住要用单例模式处理 +- **纯UI变化状态** → 组件useState,跟业务没有任何关系,只是UI变化所用的状态。 +- **业务流程状态** → UseCase私有状态,从这里开始就是跟react任何关系都没有了,纯TS代码去写的逻辑,状态也是纯粹的业务状态,一定要在这里忘记react的存在。 +- **全局状态** → 全局Store,跨模块共享,比如用户信息、主题色、语言等。 ## 数据流向 **状态流转过程**: -用户操作 → 组件本地状态 → Hook协调 → UseCase业务逻辑 → 全局状态更新 → 组件重新渲染 -**具体流程**: -1. 用户在组件中触发操作 -2. 组件更新本地状态 -3. Hook协调多个UseCase调用 -4. UseCase执行业务逻辑和规则验证 -5. 更新全局状态 -6. 组件重新渲染显示结果 +用户操作 → 组件本地状态 → Store Hook协调 → UseCase业务逻辑 → Store 状态更新 → 组件重新渲染 ## 设计模式应用 **核心模式**: -1. **依赖注入**:UseCase通过构造函数注入Service依赖 -2. **工厂模式**:创建UseCase实例,管理依赖关系 -3. **策略模式**:可插拔的业务策略(如不同的AI分析策略) -4. **观察者模式**:状态变化通知,组件响应式更新 +1. **依赖注入**:UseCase通过构造函数注入Service依赖,最好的简化useCase的方式。 +2. **策略模式**:要弄清楚,什么是策略,有了这个意识,我们这个项目,很多地方可以用到策略模式,可扩展性将提高一大截 +4. **观察者模式**:我们的项目需要跨页面,跨组件的复杂通讯,采用观察者模式是非常合适的,比如chatBox.如果搞不明白这个模式,那就事件总线,这个东西更容易理解,也能实现相同功能。 ## 🧪 测试策略 **分层测试**: -- **Component层**:组件渲染和交互测试 -- **Hook层**:状态管理和副作用测试 -- **UseCase层**:业务逻辑和规则验证测试 -- **Service层**:外部服务集成测试 -- **Repository层**:数据访问逻辑测试 +- **Component层**:组件渲染和交互测试(目前的前端测试框架,测试这个纯浪费时间,不要为这个层做单元测试) +- **Hook层**:状态管理和副作用测试(react的hook系统限制太大,不要为这个层做单元测试) +- **UseCase层**:业务逻辑和规则验证测试(这个层需要做单元测试,最适合的地方就是这个) +- **Service层**:外部服务集成测试(这个层不需要做单元测试,,因为用例层依赖这个层,如果用例层完备,这个就直接连带测试) +- **Repository层**:数据访问逻辑测试(对于前端来说,就是接口请求的一堆封装,用apifox 的mcp生成接口请求函数就好,不用再封装和创建更复杂内容。若测试这个,直接apifox) +- +>谦卑对象模式(Humble Object Pattern)是一种设计模式,用于将复杂逻辑从难以测试的组件中分离出来,以提高代码的可测试性和可维护性。其核心思想是将与用户界面、外部系统或复杂依赖相关的代码(难以测试的部分)剥离,保留一个“谦卑”的对象,只包含简单逻辑或直接调用,而将主要业务逻辑放入易于测试的独立对象中。 ## 错误处理机制 @@ -120,8 +119,8 @@ **错误处理流程**: 1. Service层捕获原始错误并转换为应用错误 2. UseCase层抛出领域错误 -3. Hook层统一捕获和处理错误 -4. 组件层显示错误信息 +3. Hook层将用例的错误,变成错误提示,展示给用户 +4. 组件层显示错误信息(组件层不需要做错误处理,因为组件层是纯展示层,不需要处理错误) ## 性能优化策略 @@ -132,19 +131,5 @@ **业务逻辑优化**: - UseCase方法设计为无状态,避免实例状态维护 -- Service层实现缓存和重试机制 -- Repository层实现数据分页和懒加载 - -## 📋 开发检查清单 - -**新增功能时**: -- [ ] 是否在正确的层次添加代码? -- [ ] 是否遵循单一职责原则? -- [ ] 是否定义了清晰的接口? -- [ ] 是否处理了错误情况? -- [ ] 是否添加了相应的测试? - -**重构代码时**: -- [ ] 是否保持了接口的向后兼容? -- [ ] 是否更新了相关的测试? -- [ ] 是否验证了功能完整性? +- 应用起多个微型的Store,不再让UI组件里面出现一大堆的state,这样可以让我们放心的拆分子组件,而状态和函数可以直接使用,而不是props传递一大堆,回调一大堆,注意store只存状态,不做任何业务逻辑,业务逻辑要写在useCase里面,store中的函数是交互事件+状态修改。 +- 全局定义统一的tailwindcss的一些样式:圆角、边框、主题色、阴影。不要再像是现在这样每次都cursor随意发挥 diff --git a/app/service/usecase/RoleEditUseCase.ts b/app/service/usecase/RoleEditUseCase.ts index 2a05478..1989f93 100644 --- a/app/service/usecase/RoleEditUseCase.ts +++ b/app/service/usecase/RoleEditUseCase.ts @@ -44,7 +44,7 @@ export class RoleEditUseCase { }); if (response.successful) { - const roleList = this.parseProjectRoleList(response.data); + const roleList = this.parseProjectRoleList(response.data.project_characters); console.log('roleList', roleList) return roleList; } else { diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 512d643..247756f 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -19,7 +19,7 @@ export default function SignupPage() { const [confirmPasswordError, setConfirmPasswordError] = useState(""); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [agreeToTerms, setAgreeToTerms] = useState(false); + const [agreeToTerms, setAgreeToTerms] = useState(true); const router = useRouter(); /** Password validation function with English prompts */ @@ -124,7 +124,7 @@ export default function SignupPage() { router.push("/login?registered=true"); } catch (error: any) { console.error("Signup error:", error); - setFormError(error.msg || "Registration failed, please try again"); + setFormError(error.message||error.msg || "Registration failed, please try again"); } finally { setIsSubmitting(false); } @@ -342,10 +342,9 @@ export default function SignupPage() { !!passwordError || !!confirmPasswordError || !password || - !confirmPassword || - !agreeToTerms + !confirmPassword } - className="flex-1 py-3 rounded-lg cursor-pointer bg-[#C039F6] hover:bg-[#C039F6]/80 text-white font-medium transition-colors disabled:opacity-70" + className="flex-1 py-3 rounded-lg cursor-pointer bg-[#C039F6] hover:bg-[#C039F6]/80 text-white font-medium transition-colors disabled:opacity-70 disabled:cursor-not-allowed" > {isSubmitting ? "Signing up..." : "Sign Up"} @@ -369,12 +368,7 @@ export default function SignupPage() {

Already have an account?{" "} - - Login - +

diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index d74bd03..8e4b474 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -17,6 +17,7 @@ import { ImagePlay, Sparkles, Settings, + MoreHorizontal, } from "lucide-react"; import { Dropdown, @@ -812,6 +813,24 @@ export function ChatInputBox({ noData }: { noData: boolean }) { videoDuration: "1min", }); + // 从 localStorage 初始化配置 + useEffect(() => { + const savedConfig = localStorage.getItem('videoFlowConfig'); + if (savedConfig) { + try { + const parsed = JSON.parse(savedConfig); + setConfigOptions({ + mode: parsed.mode || "auto", + resolution: parsed.resolution || "720p", + language: parsed.language || "english", + videoDuration: parsed.videoDuration || "1min", + }); + } catch (error) { + console.warn('解析保存的配置失败,使用默认配置:', error); + } + } + }, []); + // 配置项显示控制状态 const [showConfigOptions, setShowConfigOptions] = useState(false); @@ -918,9 +937,16 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
- setConfigOptions((prev) => ({ ...prev, [key]: value })) - } + onConfigChange={(key, value) => { + setConfigOptions((prev) => { + const newConfig = { ...prev, [key]: value }; + // 保存到 localStorage + if (typeof window !== 'undefined') { + localStorage.setItem('videoFlowConfig', JSON.stringify(newConfig)); + } + return newConfig; + }); + }} />
)} @@ -943,7 +969,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { {!isExpanded && (
{/* 第一行:输入框 */} -
+
{/* 文本输入框 - 改为textarea */}