-
Notifications
You must be signed in to change notification settings - Fork 5.1k
t #747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
t #747
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,24 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useRef } from "react"; | ||
| import { useParams, useRouter } from "next/navigation"; | ||
| import { | ||
| ResizablePanelGroup, | ||
| ResizablePanel, | ||
| ResizableHandle, | ||
| } from "@/components/ui/resizable"; | ||
| import { EditorHeader } from "@/components/editor/editor-header"; | ||
| import { MediaPanel } from "@/components/editor/media-panel"; | ||
| import { Onboarding } from "@/components/editor/onboarding"; | ||
| import { PreviewPanel } from "@/components/editor/preview-panel"; | ||
| import { PropertiesPanel } from "@/components/editor/properties-panel"; | ||
| import { Timeline } from "@/components/editor/timeline"; | ||
| import { PreviewPanel } from "@/components/editor/preview-panel"; | ||
| import { EditorHeader } from "@/components/editor/editor-header"; | ||
| import { usePanelStore } from "@/stores/panel-store"; | ||
| import { useProjectStore } from "@/stores/project-store"; | ||
| import { EditorProvider } from "@/components/providers/editor-provider"; | ||
| import { | ||
| ResizableHandle, | ||
| ResizablePanel, | ||
| ResizablePanelGroup, | ||
| } from "@/components/ui/resizable"; | ||
| import { usePlaybackControls } from "@/hooks/use-playback-controls"; | ||
| import { Onboarding } from "@/components/editor/onboarding"; | ||
| import { usePanelStore } from "@/stores/panel-store"; | ||
| import { useProjectStore } from "@/stores/project-store"; | ||
| import { useParams, useRouter, useSearchParams } from "next/navigation"; | ||
| import { Suspense, useEffect, useRef } from "react"; | ||
|
|
||
| export default function Editor() { | ||
| function EditorContent() { | ||
| const { | ||
| toolsPanel, | ||
| previewPanel, | ||
|
|
@@ -43,12 +43,100 @@ export default function Editor() { | |
| } = useProjectStore(); | ||
| const params = useParams(); | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const projectId = params.project_id as string; | ||
| const handledProjectIds = useRef<Set<string>>(new Set()); | ||
| const isInitializingRef = useRef<boolean>(false); | ||
|
|
||
| usePlaybackControls(); | ||
|
|
||
| // 处理从 nd-super-agent 传递过来的参数 | ||
| useEffect(() => { | ||
| const handleExternalParams = async () => { | ||
| console.log('🔍 开始检查外部参数...'); | ||
|
|
||
| // 获取URL参数 | ||
| const token = searchParams.get('token'); | ||
| const userId = searchParams.get('user_id'); | ||
| const tenantId = searchParams.get('tenant_id'); | ||
| const materialIds = searchParams.get('material_ids'); | ||
|
|
||
| // 从环境变量获取 API 基础 URL | ||
| const apiBaseUrl = process.env.NEXT_PUBLIC_ND_SUPER_AGENT_API_URL; | ||
|
|
||
| console.log('📋 外部参数检查:', { | ||
| hasToken: !!token, | ||
| hasUserId: !!userId, | ||
| hasTenantId: !!tenantId, | ||
| hasMaterialIds: !!materialIds, | ||
| hasApiBaseUrl: !!apiBaseUrl, | ||
| materialIds | ||
| }); | ||
|
|
||
|
|
||
| // 如果有外部参数,处理素材加载 | ||
| if (token && materialIds && apiBaseUrl) { | ||
| console.log('检测到外部参数,开始加载素材:', { | ||
| token: token.substring(0, 10) + '...', | ||
| userId, | ||
| tenantId, | ||
| materialIds, | ||
| apiBaseUrl: apiBaseUrl?.substring(0, 30) + '...' | ||
| }); | ||
|
|
||
| try { | ||
| // 解析素材ID列表 | ||
| const ids = materialIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); | ||
|
|
||
| if (ids.length > 0) { | ||
| console.log('🔗 调用素材接口:', { ids, apiUrl: `${apiBaseUrl}/gallery/batch-query` }); | ||
|
|
||
| // 调用 nd-super-agent 的素材接口 | ||
| const response = await fetch(`${apiBaseUrl}/gallery/batch-query`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Authorization': `Bearer ${token}`, | ||
| ...(tenantId && { 'X-Tenant-ID': tenantId }), | ||
| ...(userId && { 'X-User-ID': userId }) | ||
| }, | ||
| body: JSON.stringify({ ids }) | ||
|
Comment on lines
+59
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep auth context out of client-side query params and logs. This effect reads As per coding guidelines, 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| if (response.ok) { | ||
| const result = await response.json(); | ||
| console.log('✅ 素材加载成功:', { | ||
| 找到数量: result.data?.found_count || 0, | ||
| 请求数量: result.data?.requested_count || 0 | ||
| }); | ||
|
|
||
| if (result.success && result.data && result.data.items) { | ||
| console.log('📋 获取到素材:', result.data.items.map(item => ({ | ||
| id: item.id, | ||
| name: item.name || item.title, | ||
| type: item.type | ||
| }))); | ||
|
|
||
| // TODO: 将素材添加到 OpenCut 的媒体存储中 | ||
| // 可能需要调用 useMediaStore 或相关的存储管理 | ||
| } | ||
|
Comment on lines
+106
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The success path still drops the fetched assets. After Based on learnings, utilize Zustand stores from 🤖 Prompt for AI Agents |
||
| } else { | ||
| console.error('❌ 素材加载失败:', response.status, response.statusText); | ||
| } | ||
| } else { | ||
| console.warn('⚠️ 没有有效的素材ID'); | ||
| } | ||
| } catch (error) { | ||
| console.error('💥 素材加载失败:', error.message); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // 延迟执行,确保组件已经初始化 | ||
| const timer = setTimeout(handleExternalParams, 1000); | ||
| return () => clearTimeout(timer); | ||
| }, [searchParams]); | ||
|
|
||
| useEffect(() => { | ||
| let isCancelled = false; | ||
|
|
||
|
|
@@ -457,3 +545,20 @@ export default function Editor() { | |
| </EditorProvider> | ||
| ); | ||
| } | ||
|
|
||
| export default function Editor() { | ||
| return ( | ||
| <Suspense | ||
| fallback={ | ||
| <div className="h-screen w-screen flex items-center justify-center bg-background"> | ||
| <div className="flex flex-col items-center gap-4"> | ||
| <div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" /> | ||
| <p className="text-sm text-muted-foreground">正在加载编辑器...</p> | ||
| </div> | ||
| </div> | ||
| } | ||
| > | ||
| <EditorContent /> | ||
| </Suspense> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| "use client"; | ||
|
|
||
| import { useProjectStore } from "@/stores/project-store"; | ||
| import { Loader2 } from "lucide-react"; | ||
| import { useRouter, useSearchParams } from "next/navigation"; | ||
| import { Suspense, useEffect } from "react"; | ||
|
|
||
| function EditorIndexContent() { | ||
| const { createNewProject } = useProjectStore(); | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
|
|
||
| useEffect(() => { | ||
| const createAndRedirect = async () => { | ||
| try { | ||
| // 创建新项目 | ||
| const projectId = await createNewProject("新项目"); | ||
|
|
||
| // 保留查询参数 | ||
| const queryString = searchParams.toString(); | ||
| const redirectUrl = queryString | ||
| ? `/editor/${projectId}?${queryString}` | ||
| : `/editor/${projectId}`; | ||
|
|
||
| // 重定向到编辑器页面 | ||
| router.replace(redirectUrl); | ||
| } catch (error) { | ||
| console.error("创建项目失败:", error); | ||
| // 如果创建失败,重定向到项目列表页 | ||
| const queryString = searchParams.toString(); | ||
| const fallbackUrl = queryString | ||
| ? `/projects?${queryString}` | ||
| : "/projects"; | ||
| router.replace(fallbackUrl); | ||
| } | ||
| }; | ||
|
|
||
| createAndRedirect(); | ||
| }, [createNewProject, router, searchParams]); | ||
|
Comment on lines
+6
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Locate the file and verify content
fd "page.tsx" apps/web/src/app/editor/ --type fRepository: OpenCut-app/OpenCut Length of output: 141 🏁 Script executed: # Search for createNewProject implementation
rg "createNewProject" --type ts --type tsx -A 5 -B 2Repository: OpenCut-app/OpenCut Length of output: 90 🏁 Script executed: # Find the project store file to understand createNewProject
fd "project.*store" --type ts --type tsxRepository: OpenCut-app/OpenCut Length of output: 232 🏁 Script executed: # Check for useRouter and useSearchParams imports in the file
rg "useRouter|useSearchParams" apps/web/src/app/editor/ --type tsx -B 2 -A 2Repository: OpenCut-app/OpenCut Length of output: 90 🏁 Script executed: # Read the page.tsx file
cat -n apps/web/src/app/editor/page.tsxRepository: OpenCut-app/OpenCut Length of output: 2494 🏁 Script executed: # Search for createNewProject across all files
rg "createNewProject" -A 5 -B 2Repository: OpenCut-app/OpenCut Length of output: 5381 🏁 Script executed: # Find project store
fd "project" -type f -name "*store*"Repository: OpenCut-app/OpenCut Length of output: 233 🏁 Script executed: # Read the project store file to see full createNewProject implementation
cat -n apps/web/src/stores/project-store.ts | head -200Repository: OpenCut-app/OpenCut Length of output: 7468 🏁 Script executed: # See more of the project-store
wc -l apps/web/src/stores/project-store.tsRepository: OpenCut-app/OpenCut Length of output: 103 🏁 Script executed: # Read lines 200-230 to see the rest of createNewProject
sed -n '200,230p' apps/web/src/stores/project-store.tsRepository: OpenCut-app/OpenCut Length of output: 950 🏁 Script executed: # Also check how other files use createNewProject with guards
rg -B 10 -A 10 "createAndRedirect|hasStarted"Repository: OpenCut-app/OpenCut Length of output: 2792 🏁 Script executed: # Verify if there are any other guards or mechanisms in related code
rg "useRef.*false|once.*true|completed" apps/web/src/app/editor/ -A 3Repository: OpenCut-app/OpenCut Length of output: 325 🏁 Script executed: # See how isInitializingRef is used in the [project_id]/page.tsx
rg -B 5 -A 15 "isInitializingRef" apps/web/src/app/editor/Repository: OpenCut-app/OpenCut Length of output: 7788 Guard the mount effect to prevent duplicate project creation. This route executes a non-idempotent write ( Fix using ref-based guard-import { Suspense, useEffect } from "react";
+import { Suspense, useEffect, useRef } from "react";
function EditorIndexContent() {
const { createNewProject } = useProjectStore();
const router = useRouter();
const searchParams = useSearchParams();
+ const hasStartedCreationRef = useRef(false);
useEffect(() => {
+ if (hasStartedCreationRef.current) {
+ return;
+ }
+ hasStartedCreationRef.current = true;
+
const createAndRedirect = async () => {
try {
// 创建新项目
const projectId = await createNewProject("新项目");🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <div className="h-screen w-screen flex items-center justify-center bg-background"> | ||
| <div className="flex flex-col items-center gap-4"> | ||
| <Loader2 className="h-8 w-8 animate-spin text-primary" /> | ||
| <p className="text-sm text-muted-foreground">正在创建新项目...</p> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default function EditorIndex() { | ||
| return ( | ||
| <Suspense | ||
| fallback={ | ||
| <div className="h-screen w-screen flex items-center justify-center bg-background"> | ||
| <div className="flex flex-col items-center gap-4"> | ||
| <Loader2 className="h-8 w-8 animate-spin text-primary" /> | ||
| <p className="text-sm text-muted-foreground">正在加载...</p> | ||
| </div> | ||
| </div> | ||
| } | ||
| > | ||
| <EditorIndexContent /> | ||
| </Suspense> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { Footer } from "@/components/footer"; | ||
| import { Header } from "@/components/header"; | ||
| import { Hero } from "@/components/landing/hero"; | ||
| import { SITE_URL } from "@/constants/site"; | ||
| import type { Metadata } from "next"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "OpenCut - 免费开源视频编辑器", | ||
| description: "一个免费、开源的网页、桌面和移动端视频编辑器。保护隐私,功能完整,无水印。", | ||
| alternates: { | ||
| canonical: `${SITE_URL}/landing`, | ||
| }, | ||
| }; | ||
|
Comment on lines
+7
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded Chinese metadata may be inconsistent with the rest of the application. The title and description are in Chinese while other parts of the application appear to use English. If this is intentional for a specific locale, consider using i18n/l10n patterns for consistency. Otherwise, update to English to match the rest of the site. 🤖 Prompt for AI Agents |
||
|
|
||
| export default async function LandingPage() { | ||
| return ( | ||
| <div> | ||
| <Header /> | ||
| <Hero /> | ||
| <Footer /> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| import { Hero } from "@/components/landing/hero"; | ||
| import { Header } from "@/components/header"; | ||
| import { Footer } from "@/components/footer"; | ||
| import type { Metadata } from "next"; | ||
| import { SITE_URL } from "@/constants/site"; | ||
| import { redirect } from "next/navigation"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| alternates: { | ||
| canonical: SITE_URL, | ||
| }, | ||
| }; | ||
| interface HomeProps { | ||
| searchParams: Promise<{ [key: string]: string | string[] | undefined }>; | ||
| } | ||
|
|
||
| export default async function Home() { | ||
| return ( | ||
| <div> | ||
| <Header /> | ||
| <Hero /> | ||
| <Footer /> | ||
| </div> | ||
| ); | ||
| export default async function Home({ searchParams }: HomeProps) { | ||
| // 在 Next.js 15 中需要 await searchParams | ||
| const params = await searchParams; | ||
|
|
||
| // 构建查询字符串 | ||
| const queryString = new URLSearchParams(); | ||
|
|
||
| for (const [key, value] of Object.entries(params)) { | ||
| if (value !== undefined) { | ||
| if (Array.isArray(value)) { | ||
| for (const v of value) { | ||
| queryString.append(key, v); | ||
| } | ||
| } else { | ||
| queryString.set(key, value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const queryStr = queryString.toString(); | ||
| const redirectUrl = queryStr ? `/editor?${queryStr}` : "/editor"; | ||
|
|
||
| redirect(redirectUrl); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the trailing newline.
dotenv-linterwill keep flagging this example file until it ends with a trailing newline.🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 36-36: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
🤖 Prompt for AI Agents