diff --git a/CLAUDE.md b/CLAUDE.md index 51459bfe..9e76fcdb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -231,3 +231,6 @@ All routes in `src/app/api/`: - Each commit should be atomic and self-contained: one task = one commit. - The .planning directory is untracked, do not attempt to commit any changes to the files in this directory. +## Web Crawling +- Always use `cf crawl` when needing to crawl or fetch website content. + diff --git a/package-lock.json b/package-lock.json index 59a9e162..d1c279af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,21 @@ { "name": "node-banana", - "version": "1.0.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "node-banana", - "version": "1.0.0", + "version": "1.1.1", "dependencies": { "@ai-sdk/google": "^3.0.13", - "@ai-sdk/react": "^3.0.51", + "@ai-sdk/react": "^3.0.118", "@google/genai": "^1.30.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@tailwindcss/postcss": "^4.1.17", "@xyflow/react": "^12.9.3", - "ai": "^6.0.49", + "ai": "^6.0.116", "autoprefixer": "^10.4.22", "jszip": "^3.10.1", "konva": "^10.0.12", @@ -62,13 +62,13 @@ "license": "MIT" }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.29", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.29.tgz", - "integrity": "sha512-zf6yXT+7DcVGWG7ntxVCYC48X/opsWlO5ePvgH8W9DaEVUtkemqKUEzBqowQ778PkZo8sqMnRfD0+fi9HamRRQ==", + "version": "3.0.66", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.66.tgz", + "integrity": "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.6", - "@ai-sdk/provider-utils": "4.0.11", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "engines": { @@ -78,6 +78,35 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.19.tgz", + "integrity": "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@ai-sdk/google": { "version": "3.0.18", "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.18.tgz", @@ -124,13 +153,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "3.0.66", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.66.tgz", - "integrity": "sha512-bYxfXaNErVDiUaNlvNXaX+3oKLAeEyHiReJd54i+JTD3HEgRuazHfggzL0MidPnFJmlSZDHRRTGXxhMYh426QA==", + "version": "3.0.118", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.118.tgz", + "integrity": "sha512-fBAix8Jftxse6/2YJnOFkwW1/O6EQK4DK68M9DlFmZGAzBmsaHXEPVS77sVIlkaOWCy11bE7434NAVXRY+3OsQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider-utils": "4.0.11", - "ai": "6.0.64", + "@ai-sdk/provider-utils": "4.0.19", + "ai": "6.0.116", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -141,6 +170,35 @@ "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, + "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.19.tgz", + "integrity": "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -3271,14 +3329,14 @@ } }, "node_modules/ai": { - "version": "6.0.64", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.64.tgz", - "integrity": "sha512-eH+6FC2THf0rXamPOBAPGNBuMvqv4gE5+IKoWVBrO1TkrjwRiuP3+P6yl7/HcXzN9JLbVe3lL8yaTIfn0ZwvxQ==", + "version": "6.0.116", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.116.tgz", + "integrity": "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.29", - "@ai-sdk/provider": "3.0.6", - "@ai-sdk/provider-utils": "4.0.11", + "@ai-sdk/gateway": "3.0.66", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "engines": { @@ -3288,6 +3346,35 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/ai/node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ai/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.19.tgz", + "integrity": "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", diff --git a/package.json b/package.json index 33752622..b48cd56c 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ }, "dependencies": { "@ai-sdk/google": "^3.0.13", - "@ai-sdk/react": "^3.0.51", + "@ai-sdk/react": "^3.0.118", "@google/genai": "^1.30.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@tailwindcss/postcss": "^4.1.17", "@xyflow/react": "^12.9.3", - "ai": "^6.0.49", + "ai": "^6.0.116", "autoprefixer": "^10.4.22", "jszip": "^3.10.1", "konva": "^10.0.12", diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index 29d15a54..f8e6b0f1 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -22,7 +22,7 @@ import { generateWithWaveSpeed } from "./providers/wavespeed"; // Re-export for backward compatibility (test file imports from route) export const clearFalInputMappingCache = _clearFalInputMappingCache; -export const maxDuration = 300; // 5 minute timeout (Vercel hobby plan limit) +export const maxDuration = 800; // ~13 minute timeout (Vercel Pro limit) export const dynamic = 'force-dynamic'; // Ensure this route is always dynamic diff --git a/src/components/ProjectSetupModal.tsx b/src/components/ProjectSetupModal.tsx index 2aa64262..abfb6106 100644 --- a/src/components/ProjectSetupModal.tsx +++ b/src/components/ProjectSetupModal.tsx @@ -144,6 +144,11 @@ export function ProjectSetupModal({ // Inline parameters hook const { inlineParametersEnabled, setInlineParameters } = useInlineParameters(); + // Detect web deployment (not localhost) + const isWebDeployment = typeof window !== "undefined" && + !window.location.hostname.includes("localhost") && + !window.location.hostname.includes("127.0.0.1"); + // Tab state const [activeTab, setActiveTab] = useState<"project" | "providers" | "nodeDefaults" | "canvas">("project"); @@ -269,14 +274,15 @@ export function ProjectSetupModal({ return; } - if (!directoryPath.trim()) { + // Skip directory validation for web deployments + if (!isWebDeployment && !directoryPath.trim()) { setError("Project directory is required"); return; } - const fullProjectPath = ensureProjectSubfolderPath(directoryPath, name); + const fullProjectPath = isWebDeployment ? "" : ensureProjectSubfolderPath(directoryPath, name); - if (!(fullProjectPath.startsWith("/") || /^[A-Za-z]:[\\\/]/.test(fullProjectPath) || fullProjectPath.startsWith("\\\\"))) { + if (!isWebDeployment && !(fullProjectPath.startsWith("/") || /^[A-Za-z]:[\\\/]/.test(fullProjectPath) || fullProjectPath.startsWith("\\\\"))) { setError("Project directory must be an absolute path (starting with /, a drive letter, or a UNC path)"); return; } @@ -285,21 +291,24 @@ export function ProjectSetupModal({ setError(null); try { - // Validate path shape when it already exists - const response = await fetch( - `/api/workflow?path=${encodeURIComponent(fullProjectPath)}` - ); - const result = await response.json(); - - if (result.exists && !result.isDirectory) { - setError("Project path is not a directory"); - setIsValidating(false); - return; + // Skip path validation for web deployments + if (!isWebDeployment) { + // Validate path shape when it already exists + const response = await fetch( + `/api/workflow?path=${encodeURIComponent(fullProjectPath)}` + ); + const result = await response.json(); + + if (result.exists && !result.isDirectory) { + setError("Project path is not a directory"); + setIsValidating(false); + return; + } } const id = mode === "new" ? generateWorkflowId() : useWorkflowStore.getState().workflowId || generateWorkflowId(); - // Update external storage setting - setUseExternalImageStorage(externalStorage); + // Update external storage setting - force embedded images for web deployments + setUseExternalImageStorage(isWebDeployment ? false : externalStorage); onSave(id, name.trim(), fullProjectPath); setIsValidating(false); } catch (err) { @@ -449,51 +458,64 @@ export function ProjectSetupModal({ /> -
- -
- setDirectoryPath(e.target.value)} - placeholder="/Users/username/projects/my-project" - className="flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded-lg text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" - /> - + {isWebDeployment ? ( +
+

+ Web Mode: Workflows run in memory only. Files cannot be saved to disk. +

+

+ Images will be embedded as base64. Use the local app for file saving. +

-

- Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created. -

-
- -
- -
+ +
+ +
+ + )}