diff --git a/.gitignore b/.gitignore index 43903825..43d5506e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ storage/ __pycache__/ .claude/ .vscode/ +.trae/ +dist/ # 使用通配符 *.log diff --git a/folder-alias.json b/folder-alias.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/folder-alias.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/private-folder-alias.json b/private-folder-alias.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/private-folder-alias.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/video-gen-api/.env b/video-gen-api/.env index 4c3bba2f..d64f4fbb 100644 --- a/video-gen-api/.env +++ b/video-gen-api/.env @@ -8,7 +8,7 @@ SECRET_KEY=local-dev-secret-key-not-for-production DATABASE_URL=postgresql+asyncpg://videogen:7k33pnXdPL62Yyb4@180.184.42.66:5432/videogen # Redis (leave empty to disable - rate limiting and captcha will use in-memory fallback) -REDIS_URL=redis://localhost:6379/0 +REDIS_URL=redis://localhost:6379/3 # JWT JWT_ALGORITHM=HS256 diff --git a/video-gen-app/folder-alias.json b/video-gen-app/folder-alias.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/video-gen-app/folder-alias.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/video-gen-app/package-lock.json b/video-gen-app/package-lock.json index 488e1e75..48e91e00 100644 --- a/video-gen-app/package-lock.json +++ b/video-gen-app/package-lock.json @@ -12,6 +12,7 @@ "antd": "^6.3.7", "dayjs": "^1.11.20", "plyr-react": "^6.0.0", + "qrcode.react": "^4.2.0", "react": "^19.2.5", "react-dom": "^19.2.5", "react-router-dom": "^7.15.0", @@ -3997,6 +3998,15 @@ "node": ">=6" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/video-gen-app/package.json b/video-gen-app/package.json index 0b264020..cc5a4029 100644 --- a/video-gen-app/package.json +++ b/video-gen-app/package.json @@ -14,6 +14,7 @@ "antd": "^6.3.7", "dayjs": "^1.11.20", "plyr-react": "^6.0.0", + "qrcode.react": "^4.2.0", "react": "^19.2.5", "react-dom": "^19.2.5", "react-router-dom": "^7.15.0", diff --git a/video-gen-app/private-folder-alias.json b/video-gen-app/private-folder-alias.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/video-gen-app/private-folder-alias.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/video-gen-app/src/App.tsx b/video-gen-app/src/App.tsx index d5b10537..57372339 100644 --- a/video-gen-app/src/App.tsx +++ b/video-gen-app/src/App.tsx @@ -8,10 +8,24 @@ import ProjectsPage from './pages/ProjectsPage'; import GeneratePage from './pages/GeneratePage'; import RecordsPage from './pages/RecordsPage'; import CreditsPage from './pages/CreditsPage'; +import GenerateConver from './pages/GenerateConver'; +import InitialReplication from './pages/InitialReplication'; +import RemoveLens from './pages/RemoveLens'; + + + + import { useAuthStore } from './store/useAuthStore'; const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { - const { user, loading } = useAuthStore(); + const { user, loading, checkAuth } = useAuthStore(); + + useEffect(() => { + const token = localStorage.getItem('auth_token'); + if (!token && !loading && !user) { + window.location.href = '/login'; + } + }, [user, loading]); if (loading) { return ( @@ -20,11 +34,10 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { ); } - if (!user) { - return ; + window.location.href = '/login'; + return null; } - return <>{children}; }; @@ -75,6 +88,12 @@ const App = () => { } /> } /> } /> + } /> + } /> + } /> + + + } /> diff --git a/video-gen-app/src/api/client.ts b/video-gen-app/src/api/client.ts index c23a4d7a..9deb7932 100644 --- a/video-gen-app/src/api/client.ts +++ b/video-gen-app/src/api/client.ts @@ -106,10 +106,11 @@ export async function apiRequest(path: string, options: RequestOptions = {}): } // Handle error responses - if (!res.ok) { +if (!res.ok) { const msg = parsed?.detail || `请求失败 (${res.status})`; if (res.status === 401) { clearToken(); + window.location.href = '/login'; } throw new Error(msg); } diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 90fee567..3c833cf1 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -77,11 +77,15 @@ export async function optimizePrompt( ): Promise { if (USE_MOCK) return mock.mockOptimizePrompt(projectId, params as any); return api.post('/generation-records/optimize', { - project_id: projectId, + project_id: projectId,//项目id + gen_type:params.genType,//生成类型 prompt: params.prompt, duration: params.duration, references: params.references || null, idempotency_key: params.idempotencyKey || null, + image_size: params.image_size || null, + image_proportion: params.image_proportion || null, + image_px: params.image_px || null, }); } @@ -160,8 +164,13 @@ export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: strin export async function getVideoEngines(): Promise<{ items: { id: string; name: string; provider: string; supportedRatios: string[]; supportedResolutions: string[]; supportedDurations: number[] }[] }> { if (USE_MOCK) return { items: [{ id: 'mock', name: 'Seedance', provider: 'seedance', supportedRatios: ['16:9', '9:16', '1:1', '4:3', '3:4', '21:9'], supportedResolutions: ['480p', '720p', '1080p'], supportedDurations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] }] }; - return api.get('/video-engines', false); + return api.get('/video-engines'); } +// 参数选择(图片)Parameters +export async function getParameters(): Promise { + return api.get('/image-engines'); +} + // ── SMS ─────────────────────────────────────────────────── @@ -266,3 +275,11 @@ export async function getMenuConfigs(): Promise { export async function getRechargePackages(): Promise { return api.get('/recharge-packages'); } + + +export async function getCreditRatios(): Promise { + return api.get('/credits/ratios'); +} + + + diff --git a/video-gen-app/src/api/mock.ts b/video-gen-app/src/api/mock.ts index af722496..88b8669d 100644 --- a/video-gen-app/src/api/mock.ts +++ b/video-gen-app/src/api/mock.ts @@ -3,6 +3,7 @@ import type { CreditRecord, Project, GenerationRecord, + GenerateParams, OptimizeParams, OptimizeResult, LoginParams, @@ -55,10 +56,14 @@ let MOCK_RECORDS: GenerationRecord[] = [ resolution: '1080p', status: 'completed', videoUrl: 'https://example.com/video1.mp4', - textCreditsCost: 10, - textTokensUsed: 850, creditsCost: 120, - videoTokensUsed: 0, + textCreditsCost: 0, + textTokensUsed: 0, + videoTokensUsed: 120, + imageSize: '1080p', + imageProportion: '9:16', + imagePx: '1080x1920', + imageUrl: '', createdAt: '2026-04-29 14:22:00', generatedAt: '2026-04-29 14:25:00', }, @@ -73,10 +78,14 @@ let MOCK_RECORDS: GenerationRecord[] = [ resolution: '1080p', status: 'completed', videoUrl: 'https://example.com/video2.mp4', - textCreditsCost: 8, - textTokensUsed: 720, creditsCost: 80, - videoTokensUsed: 0, + textCreditsCost: 0, + textTokensUsed: 0, + videoTokensUsed: 80, + imageSize: '1080p', + imageProportion: '16:9', + imagePx: '1920x1080', + imageUrl: '', createdAt: '2026-04-30 09:15:00', generatedAt: '2026-04-30 09:18:00', }, @@ -90,10 +99,14 @@ let MOCK_RECORDS: GenerationRecord[] = [ aspectRatio: '16:9', resolution: '4K', status: 'prompt_optimized', - textCreditsCost: 12, - textTokensUsed: 960, creditsCost: 120, - videoTokensUsed: 0, + textCreditsCost: 0, + textTokensUsed: 0, + videoTokensUsed: 120, + imageSize: '4K', + imageProportion: '16:9', + imagePx: '3840x2160', + imageUrl: '', createdAt: '2026-05-01 16:40:00', }, { @@ -106,10 +119,14 @@ let MOCK_RECORDS: GenerationRecord[] = [ aspectRatio: '16:9', resolution: '4K', status: 'prompt_optimized', - textCreditsCost: 10, - textTokensUsed: 880, creditsCost: 100, - videoTokensUsed: 0, + textCreditsCost: 0, + textTokensUsed: 0, + videoTokensUsed: 100, + imageSize: '4K', + imageProportion: '16:9', + imagePx: '3840x2160', + imageUrl: '', createdAt: '2026-05-03 08:30:00', }, ]; @@ -169,15 +186,14 @@ export async function mockDeleteProject(id: string): Promise { export async function mockOptimizePrompt( projectId: string, params: OptimizeParams -): Promise { +): Promise { await delay(1500); const project = MOCK_PROJECTS.find((p) => p.id === projectId); - const textCredits = Math.max(1, Math.ceil(params.prompt.length * 0.3)); - const textTokens = Math.round(params.prompt.length * 1.2); + const cost = Math.round(80 + params.prompt.length * 0.5 + params.duration * 2); if (currentUser) { - currentUser.credits -= textCredits; + currentUser.credits -= cost; } const optimizedPromptMap: Record = { @@ -195,7 +211,7 @@ export async function mockOptimizePrompt( if (matched) { optimizedPrompt = optimizedPromptMap[matched]; } else { - optimizedPrompt = `精心构图的画面中,${params.prompt}。采用电影级镜头语言,自然光线与人工光源完美结合,营造出沉浸式视觉体验。画面色彩饱满而真实,细节丰富。运镜流畅自然,节奏张弛有度,完美适配${params.duration}秒时长。`; + optimizedPrompt = `精心构图的画面中,${params.prompt}。采用电影级镜头语言,自然光线与人工光源完美结合,营造出沉浸式视觉体验。画面色彩饱满而真实,细节丰富。运镜流畅自然,节奏张弛有度,完美适配${params.duration}秒时长。${params.aspectRatio}比例构图,${params.resolution}高清画质呈现。`; } const record: GenerationRecord = { @@ -205,17 +221,23 @@ export async function mockOptimizePrompt( originalPrompt: params.prompt, optimizedPrompt, duration: params.duration, + aspectRatio: params.aspectRatio as any, + resolution: params.resolution as any, status: 'prompt_optimized', - textCreditsCost: textCredits, - textTokensUsed: textTokens, - creditsCost: 0, + creditsCost: cost, + textCreditsCost: cost, + textTokensUsed: 0, videoTokensUsed: 0, + imageSize: params.resolution || '1080p', + imageProportion: params.aspectRatio || '16:9', + imagePx: '1920x1080', + imageUrl: '', createdAt: new Date().toLocaleString('zh-CN'), }; MOCK_RECORDS.unshift(record); - return { optimizedPrompt, textCreditsCost: textCredits, textTokensUsed: textTokens, record }; + return { optimizedPrompt, textCreditsCost: cost, textTokensUsed: 0, record }; } export async function mockGenerateVideo(recordId: string): Promise { @@ -241,10 +263,10 @@ export async function mockGetGenerationRecords(projectId?: string): Promise { const [unreadCount, setUnreadCount] = useState(0); const [siteName, setSiteName] = useState('VideoGen.AI'); const [siteLogo, setSiteLogo] = useState(''); + const [qrCodeModalOpen, setQrCodeModalOpen] = useState(false); + const [currentPaymentInfo, setCurrentPaymentInfo] = useState<{ price: number; credits: number; qrCode: string } | null>(null); useEffect(() => { getSiteInfo().then(info => { @@ -240,7 +244,7 @@ const AppLayout: React.FC = () => { {/* Menu */} -
+
{!collapsed && (
导航 @@ -500,7 +504,27 @@ const AppLayout: React.FC = () => {
+ {/* QR Code Payment Modal */} + { setQrCodeModalOpen(false); setCurrentPaymentInfo(null); }} + footer={null} + width={400} + closable={false} + styles={{ + body: { padding: 0, borderRadius: 16, overflow: 'hidden' }, + }} + > +
+ {/* Header */} +
+
+ +
+ 微信支付 + 请使用微信扫描二维码完成支付 +
+ + {/* QR Code */} +
+
+ {currentPaymentInfo && ( + + )} +
+
+
+ ¥{currentPaymentInfo?.price || 0} +
+
+ 购买 {currentPaymentInfo?.credits || 0} 积分 +
+
+
+ + {/* Tips */} +
+
+
💡
+
+
支付提示
+
    +
  • 请在支付后等待页面自动跳转
  • +
  • 如支付成功但未到账,请联系客服
  • +
+
+
+
+ + {/* Footer Buttons */} +
+ + +
+
+
+ {/* Message Center Modal */} = ({ value, const elapsed = now - startTime; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); - setDisplay(Math.round(start + diff * eased)); + setDisplay(start + diff * eased); if (progress < 1) ref.current = requestAnimationFrame(animate); }; ref.current = requestAnimationFrame(animate); diff --git a/video-gen-app/src/pages/GenerateConver.tsx b/video-gen-app/src/pages/GenerateConver.tsx new file mode 100644 index 00000000..2f339269 --- /dev/null +++ b/video-gen-app/src/pages/GenerateConver.tsx @@ -0,0 +1,189 @@ +import React, { useState } from 'react'; +import { + Layout, + Button, + Input, + Select, + Upload, + message, + Space, + Typography, +} from 'antd'; +import { + PlusOutlined, + MenuUnfoldOutlined, + MenuFoldOutlined, + SendOutlined, + UploadOutlined, +} from '@ant-design/icons'; + +const { Header, Sider, Content } = Layout; +const { TextArea } = Input; +const { Option } = Select; +const { Text } = Typography; + +const AIChatPage: React.FC = () => { + // 侧边栏收起/展开状态 + const [collapsed, setCollapsed] = useState(false); + // 输入框内容 + const [inputValue, setInputValue] = useState(''); + // 第一个选择器:文件类型(图片/视频) + const [mediaType, setMediaType] = useState('image'); + // 第二个选择器:1/2/3 + const [countType, setCountType] = useState('1'); + + // 模拟“新对话”点击 + const handleNewChat = () => { + message.info('开启新对话'); + setInputValue(''); + }; + + // 发送消息 + const handleSend = () => { + if (!inputValue.trim()) { + message.warning('请输入内容'); + return; + } + console.log('发送消息:', inputValue, mediaType, countType); + message.success('消息已发送'); + setInputValue(''); + }; + + // 文件上传前校验(仅示意) + const beforeUpload = (file: File) => { + const isImageOrVideo = file.type.startsWith('image/') || file.type.startsWith('video/'); + if (!isImageOrVideo) { + message.error('只能上传图片或视频文件'); + } + return isImageOrVideo; + }; + + return ( + + {/* 左侧边栏 */} + +
+ + )} +
+
+ + {/* 主内容区 */} + +
+ 开启创作 +
+ + + {/* 中间提示语 */} +
+

你好,想创作什么?

+
+ + {/* 底部输入区域 */} +
+
+ {/* 文件上传按钮 */} + +