Merge remote-tracking branch 'origin/dev' into prod

This commit is contained in:
qikongjian 2025-09-23 20:35:55 +08:00
commit 852de5e52f
10 changed files with 353 additions and 38 deletions

View File

@ -525,6 +525,8 @@ export default function SignupPage() {
variant="outline"
size="md"
className="w-full"
label="Sign up with Google"
loadingLabel="Signing up..."
/>
</>
)}
@ -578,7 +580,7 @@ export default function SignupPage() {
</div>
<h3 className="text-xl font-semibold text-white text-center mb-2" data-alt="activation-title">Please verify your email to activate your account</h3>
<p className="text-gray-300 text-center mb-4" data-alt="activation-desc">
We have sent an activation email to {email || "your email"}; if you don't receive it, please check your spam folder or try again later
We have sent an activation email to {email || "your email"}; if you don&apos;t receive it, please check your spam folder or try again later
</p>
<div className="flex items-center justify-center mb-4 gap-3">

View File

@ -5,9 +5,10 @@ import { useRouter, useSearchParams } from "next/navigation";
import { CheckCircle, XCircle, Loader2, AlertTriangle } from "lucide-react";
import type { OAuthCallbackParams } from "@/app/types/google-oauth";
// 根据接口文档定义响应类型
// 根据后端实际返回格式定义响应类型
interface GoogleOAuthResponse {
success: boolean;
code: number;
message: string;
data: {
token: string;
user: {
@ -21,7 +22,7 @@ interface GoogleOAuthResponse {
};
message: string;
};
message?: string;
successful: boolean;
}
export default function OAuthCallback() {
@ -117,7 +118,13 @@ export default function OAuthCallback() {
console.log('Python OAuth接口响应状态:', response.status);
const result: GoogleOAuthResponse = await response.json();
if (!response.ok || !result.success) {
console.log('🔍 后端返回的完整数据:', result);
// 根据后端实际返回格式判断成功状态
// 后端返回: { code: 0, successful: true } 表示成功
const isSuccess = response.ok && result.successful && result.code === 0;
if (!isSuccess) {
console.error('Python OAuth接口处理失败:', result);
// 处理常见错误码
@ -129,21 +136,25 @@ export default function OAuthCallback() {
throw new Error('Authentication service error. Please try again later.');
}
throw new Error(result.message || 'Google OAuth failed');
// 根据后端返回格式处理错误
const errorMessage = result.message || `OAuth failed (code: ${result.code})`;
throw new Error(errorMessage);
}
console.log('Google OAuth成功:', {
console.log('🎉 Google OAuth成功:', {
userId: result.data?.user?.userId,
email: result.data?.user?.email,
isNewUser: result.data?.user?.isNewUser
isNewUser: result.data?.user?.isNewUser,
code: result.code,
successful: result.successful
});
// 处理成功结果
console.log('Google登录成功:', result);
console.log('Google登录成功,完整响应:', result);
setStatus("success");
setMessage(result.data.message || "Login successful! Redirecting to dashboard...");
// 根据接口文档的响应格式保存用户信息
// 根据后端实际响应格式保存用户信息
const { token, user } = result.data;
// 保存用户信息到localStorage
@ -157,14 +168,20 @@ export default function OAuthCallback() {
isNewUser: user.isNewUser
};
console.log('💾 保存用户数据到 localStorage:', userData);
localStorage.setItem('currentUser', JSON.stringify(userData));
if (token) {
console.log('🔑 保存 token 到 localStorage');
localStorage.setItem('token', token);
}
console.log('⏰ 准备在2秒后跳转到 /movies');
// 2秒后跳转到主页
setTimeout(() => {
const returnUrl = '/movies';
console.log('🚀 开始跳转到:', returnUrl);
window.location.href = returnUrl;
}, 2000);

View File

@ -261,8 +261,17 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
left: (pathname === "/" || !isDesktop) ? "0" : (collapsed ? "2.5rem" : "16rem")
}}
>
<div className="h-full flex items-center justify-between px-4">
<div className="flex items-center space-x-4">
<div className="h-full flex items-center justify-between pr-4 md:px-4">
<div className="flex items-center md:space-x-4">
{pathname === "/" && (
<button
data-alt="mobile-menu-toggle"
className="md:hidden text-white/90 p-[0.8rem]"
onClick={() => window.dispatchEvent(new CustomEvent('home-menu-toggle'))}
>
</button>
)}
<div
className={`flex items-center cursor-pointer space-x-1 link-logo roll event-on`}
onClick={() => router.push("/")}

View File

@ -217,6 +217,11 @@ export function HomePage2() {
const NavBar = () => {
if (homeTabs.length === 0) return null;
useEffect(() => {
const handler = () => setMenuOpen((v) => !v);
window.addEventListener('home-menu-toggle' as any, handler as any);
return () => window.removeEventListener('home-menu-toggle' as any, handler as any);
}, []);
return (
<div data-alt="home-navbar" className="fixed h-16 top-0 left-0 right-0 z-50">
<div className="mx-auto h-full">
@ -234,14 +239,8 @@ export function HomePage2() {
</button>
))}
</div>
{/* 移动端开关 */}
<button
data-alt="mobile-menu-toggle"
className="md:hidden text-white/90 px-3 py-1 border border-white/20 rounded"
onClick={() => setMenuOpen((v) => !v)}
>
</button>
{/* 移动端开关移至 TopBar保留占位对齐 */}
<span className="md:hidden" data-alt="mobile-menu-toggle-placeholder"></span>
</div>
{/* 移动端下拉(仅三个项) */}
{menuOpen && (
@ -1396,9 +1395,9 @@ function HomeModule5() {
{/* 主要价格卡片 */}
<div
className="w-full max-w-[70%] mx-auto px-4
className="w-full max-w-[88%] mx-auto px-4
/* 移动端 - 单列布局 */
grid grid-cols-1 gap-4
grid grid-cols-1 gap-2
/* 平板 - 双列布局 */
sm:grid-cols-2 sm:gap-6 sm:px-6
/* 桌面端 - 三列布局 */

View File

@ -297,6 +297,8 @@ export default function Login() {
variant="outline"
size="md"
className="w-full"
label="Sign in with Google"
loadingLabel="Signing in..."
/>
</>
)}

View File

@ -164,7 +164,9 @@ export function H5MediaViewer({
// 渲染视频 slide
const renderVideoSlides = () => (
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg">
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg" style={{
height: 'calc(100vh - 20rem)',
}}>
<Carousel
ref={carouselRef}
key={`h5-carousel-video-${stage}-${videoUrls.length}`}
@ -223,7 +225,7 @@ export function H5MediaViewer({
</>
) : (
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status" style={{
maxHeight: 'calc(100vh - 20rem)',
height: 'calc(100vh - 20rem)',
}}>
{status === 0 && (
<span className="text-blue-500 text-base">Generating...</span>
@ -248,7 +250,9 @@ export function H5MediaViewer({
// 渲染图片 slide
const renderImageSlides = () => (
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg">
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg" style={{
height: 'calc(100vh - 20rem)',
}}>
<Carousel
ref={carouselRef}
key={`h5-carousel-image-${stage}-${imageUrls.length}`}

View File

@ -11,6 +11,10 @@ interface GoogleLoginButtonProps {
className?: string;
size?: "sm" | "md" | "lg";
variant?: "default" | "outline";
/** Button label when not loading */
label?: string;
/** Button label when loading */
loadingLabel?: string;
}
// Google logo SVG component matching the exact reference
@ -83,6 +87,8 @@ export const GoogleLoginButton = React.forwardRef<
className,
size = "md",
variant = "default",
label = "Google Login",
loadingLabel = "Signing in...",
...props
},
ref
@ -147,7 +153,7 @@ export const GoogleLoginButton = React.forwardRef<
// Custom className
className
)}
aria-label={loading ? "Signing in with Google" : "Google Login"}
aria-label={loading ? loadingLabel : label}
aria-disabled={isDisabled}
role="button"
tabIndex={isDisabled ? -1 : 0}
@ -158,9 +164,7 @@ export const GoogleLoginButton = React.forwardRef<
) : (
<GoogleLogo className={sizeConfig.icon} />
)}
<span className="font-medium">
{loading ? "Signing in..." : "Google Login"}
</span>
<span className="font-medium">{loading ? loadingLabel : label}</span>
</button>
);
}

View File

@ -0,0 +1,153 @@
# OAuth 回调页面跳转问题修复总结
## 🚨 问题描述
用户反馈:调用后端 `api/oauth/google` 接口返回成功,但前端成功后没有跳转到 `/movies` 页面。
## 🔍 问题根因分析
### 后端实际返回格式:
```json
{
"code": 0,
"message": "ok",
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user": {
"userId": "0a005fe7181e40baafdd80ce3c2ce98d",
"userName": "Zixin Zhou",
"name": "Zixin Zhou",
"email": "codecaesar2020@gmail.com",
"authType": "GOOGLE",
"avatar": "https://lh3.googleusercontent.com/a/ACg8ocLS9E_7EIntgaxYFuczaU3laxooRltg2JIbnzc6osnANS8beL8=s96-c",
"isNewUser": false
},
"message": "Google OAuth successful"
},
"successful": true
}
```
### 前端原来期望的格式:
```typescript
interface GoogleOAuthResponse {
success: boolean; // ❌ 后端用的是 successful
data: { ... };
message?: string;
}
```
### 关键问题:
1. **字段名不匹配**:后端用 `successful: true`,前端检查 `success`
2. **状态码处理**:后端用 `code: 0` 表示成功,前端没有处理
3. **成功判断逻辑错误**:导致成功的响应被当作失败处理
## 🔧 修复方案
### 1. 更新响应类型定义
**修改前**
```typescript
interface GoogleOAuthResponse {
success: boolean;
data: { ... };
message?: string;
}
```
**修改后**
```typescript
interface GoogleOAuthResponse {
code: number;
message: string;
data: {
token: string;
user: { ... };
message: string;
};
successful: boolean;
}
```
### 2. 修复成功判断逻辑
**修改前**
```typescript
if (!response.ok || !result.success) {
// 错误处理
}
```
**修改后**
```typescript
// 根据后端实际返回格式判断成功状态
// 后端返回: { code: 0, successful: true } 表示成功
const isSuccess = response.ok && result.successful && result.code === 0;
if (!isSuccess) {
// 错误处理
}
```
### 3. 增强调试日志
添加了详细的调试日志来跟踪整个流程:
- 后端返回的完整数据
- 成功状态判断
- localStorage 数据保存
- 跳转准备和执行
## 📋 修改的文件
- **`app/users/oauth/callback/page.tsx`**
- 更新了 `GoogleOAuthResponse` 接口定义
- 修复了成功状态判断逻辑
- 增加了详细的调试日志
- 保持了2秒延迟跳转到 `/movies` 的逻辑
## 🧪 测试验证
修复后的流程应该是:
1. **接收后端响应**
```
🔍 后端返回的完整数据: { code: 0, successful: true, ... }
```
2. **成功状态判断**
```
🎉 Google OAuth成功: { userId: "...", email: "...", code: 0, successful: true }
```
3. **数据保存**
```
💾 保存用户数据到 localStorage: { userId: "...", userName: "...", ... }
🔑 保存 token 到 localStorage
```
4. **跳转准备**
```
⏰ 准备在2秒后跳转到 /movies
🚀 开始跳转到: /movies
```
## 🎯 预期结果
修复后,当后端返回成功响应时:
1. 前端能正确识别成功状态
2. 用户数据和 token 正确保存到 localStorage
3. 2秒后自动跳转到 `/movies` 页面
4. 控制台显示详细的调试信息
## 🔄 回滚方案
如果修复后出现问题,可以:
1. 恢复原来的接口定义
2. 联系后端开发者统一响应格式
3. 或者在前端做兼容性处理
## 📝 注意事项
- 这次修复是基于后端实际返回的数据格式
- 如果后端响应格式发生变化,需要相应更新前端代码
- 建议前后端团队统一 API 响应格式规范

View File

@ -0,0 +1,123 @@
# Video-Flow 自动下载功能禁用说明
## 📋 修改概述
根据需求,已将 Video-Flow 工作流中的自动下载视频功能暂时禁用,用户需要手动下载生成的视频。
## 🔍 工作流程梳理
### 完整的 Work-Flow 自动调用剪辑接口流程
```mermaid
graph TB
A[用户在 work-flow 页面] --> B{视频片段生成完成?}
B -->|是| C[显示 AI 剪辑按钮]
B -->|否| D[等待视频生成]
C --> E[用户点击 AI 剪辑按钮]
E --> F[ai-editing-adapter.ts 处理]
F --> G[调用 /api/export/ai-clips 接口]
G --> H[执行 AI 剪辑处理]
H --> I[生成最终视频]
I --> J[返回视频 URL]
J --> K[显示手动下载按钮]
style K fill:#90EE90
style I fill:#FFE4B5
```
### 涉及的核心文件
1. **`components/pages/work-flow.tsx`** - 主工作流页面
2. **`components/pages/work-flow/ai-editing-button.tsx`** - AI 剪辑按钮组件
3. **`components/pages/work-flow/ai-editing-adapter.ts`** - AI 剪辑适配器
4. **`utils/export-service.ts`** - 导出服务(已禁用自动下载)
5. **`app/api/export/ai-clips/route.ts`** - 剪辑接口
6. **`app/api/export/download/[exportId]/route.ts`** - 下载接口
## 🚫 已禁用的自动下载功能
### 修改位置 1: `utils/export-service.ts` (第685-701行)
**修改前**
```typescript
// 如果SSE中直接有完整结果直接处理
if (result?.download_url || result?.video_url) {
const downloadUrl = result.download_url || result.video_url;
console.log('📥 直接从SSE结果下载视频:', downloadUrl);
await downloadVideo(downloadUrl); // 🚫 自动下载
}
```
**修改后**
```typescript
// 如果SSE中直接有完整结果直接处理
if (result?.download_url || result?.video_url) {
const downloadUrl = result.download_url || result.video_url;
console.log('📥 直接从SSE结果获取视频URL:', downloadUrl);
// 🚫 暂时不支持自动下载 - 已注释
// await downloadVideo(downloadUrl);
}
```
### 修改位置 2: `utils/export-service.ts` (第726-736行)
**修改前**
```typescript
// 自动下载视频
if (finalExportResult.video_url) {
console.log('📥 开始下载视频:', finalExportResult.video_url);
await downloadVideo(finalExportResult.video_url); // 🚫 自动下载
console.log('✅ 视频下载完成');
}
```
**修改后**
```typescript
// 🚫 暂时不支持自动下载 - 已注释
if (finalExportResult.video_url) {
console.log('📥 视频导出完成URL:', finalExportResult.video_url);
// await downloadVideo(finalExportResult.video_url);
console.log('✅ 视频导出完成,用户可手动下载');
}
```
## ✅ 保留的手动下载功能
以下手动下载功能仍然保留:
### 1. AI 剪辑完成后的下载按钮
- **文件**: `components/pages/work-flow/ai-editing-button.tsx`
- **位置**: 第284-298行
- **功能**: AI 剪辑完成后显示下载按钮,用户可手动点击下载
### 2. 媒体查看器中的下载按钮
- **文件**: `components/pages/work-flow/media-viewer.tsx`
- **功能**: 用户可手动下载单个视频或所有视频
### 3. 项目列表中的下载按钮
- **文件**: `components/pages/create-to-video2.tsx`
- **功能**: 用户可在项目列表中手动下载最终视频
## 🔄 如何重新启用自动下载
如果将来需要重新启用自动下载功能,只需:
1. 在 `utils/export-service.ts` 中取消注释 `await downloadVideo(downloadUrl)`
2. 将相关的 console.log 消息改回原来的描述
3. 测试确保自动下载功能正常工作
## 📝 注意事项
- 自动下载功能的禁用不影响视频的生成和处理
- 用户仍然可以通过手动点击下载按钮来下载视频
- 所有的下载接口和相关功能都保持完整,只是不会自动触发
- 这个修改是临时的,可以随时恢复
## 🧪 测试建议
1. 测试 AI 剪辑流程是否正常工作
2. 确认剪辑完成后不会自动下载
3. 验证手动下载按钮是否正常显示和工作
4. 检查控制台日志确认修改生效

View File

@ -685,8 +685,10 @@ export class VideoExportService {
// 如果SSE中直接有完整结果直接处理
if (result?.download_url || result?.video_url) {
const downloadUrl = result.download_url || result.video_url;
console.log('📥 直接从SSE结果下载视频:', downloadUrl);
await downloadVideo(downloadUrl);
console.log('📥 直接从SSE结果获取视频URL:', downloadUrl);
// 🚫 暂时不支持自动下载 - 已注释
// await downloadVideo(downloadUrl);
// notification.success({
// message: '视频下载完成!',
@ -721,16 +723,16 @@ export class VideoExportService {
});
*/
// 自动下载视频
// 🚫 暂时不支持自动下载 - 已注释
if (finalExportResult.video_url) {
console.log('📥 开始下载视频:', finalExportResult.video_url);
console.log('📥 视频导出完成URL:', finalExportResult.video_url);
console.log('📋 视频文件信息:', {
url: finalExportResult.video_url,
file_size: finalExportResult.file_size,
quality_mode: finalExportResult.quality_mode
});
await downloadVideo(finalExportResult.video_url);
console.log('✅ 视频下载完成');
// await downloadVideo(finalExportResult.video_url);
console.log('✅ 视频导出完成,用户可手动下载');
}
// 清除缓存的请求数据