This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run dev # Start Next.js dev server at http://localhost:3000
npm run build # Build for production
npm run start # Start production server
npm run lint # Run Next.js linting
npm run test # Run all tests with Vitest (watch mode)
npm run test:run # Run all tests once (CI mode)Create .env.local in the root directory:
GEMINI_API_KEY=your_gemini_api_key
OPENAI_API_KEY=your_openai_api_key # Optional, for OpenAI LLM provider
KIE_API_KEY=your_kie_api_key # Optional, for Kie.ai models (Sora, Veo, Kling, etc.)
Node Banana is a node-based visual workflow editor for AI image generation. Users drag nodes onto a React Flow canvas, connect them via typed handles, and execute pipelines that call AI APIs.
- Next.js 16 (App Router) with TypeScript
- @xyflow/react (React Flow) for the node editor canvas
- Konva.js / react-konva for canvas annotation drawing
- Zustand for state management (single store pattern)
| Purpose | Location |
|---|---|
| Central workflow state & execution logic | src/store/workflowStore.ts |
| All TypeScript type definitions | src/types/index.ts |
| Main canvas component & connection validation | src/components/WorkflowCanvas.tsx |
| Base node component (shared by all nodes) | src/components/nodes/BaseNode.tsx |
| Image generation API route | src/app/api/generate/route.ts |
| LLM text generation API route | src/app/api/llm/route.ts |
| Cost calculations | src/utils/costCalculator.ts |
| Grid splitting utility | src/utils/gridSplitter.ts |
All application state lives in workflowStore.ts using Zustand. Key patterns:
useWorkflowStore()hook provides access to nodes, edges, and all actionsexecuteWorkflow(startFromNodeId?)runs the pipeline via topological sortgetConnectedInputs(nodeId)retrieves upstream data for a nodeupdateNodeData(nodeId, partialData)updates node state- Auto-save runs every 90 seconds when enabled
- User clicks Run or presses
Cmd/Ctrl+Enter executeWorkflow()performs topological sort on node graph- Nodes execute in dependency order, calling APIs as needed
getConnectedInputs()provides upstream images/text to each node- Locked groups are skipped; pause edges halt execution
Image generation models (these exist and are recently released):
gemini-2.5-flash-image→ internal name:nano-bananagemini-3-pro-image-preview→ internal name:nano-banana-pro
LLM models:
- Google:
gemini-2.5-flash,gemini-3-flash-preview,gemini-3-pro-preview - OpenAI:
gpt-4.1-mini,gpt-4.1-nano
| Type | Purpose | Inputs | Outputs |
|---|---|---|---|
imageInput |
Load/upload images | reference | image |
annotation |
Draw on images (Konva) | image | image |
prompt |
Text prompt input | none | text |
nanoBanana |
AI image generation | image, text | image |
llmGenerate |
AI text generation | text, image | text |
splitGrid |
Split image into grid cells | image | reference |
generateAudio |
AI audio/TTS generation | text | audio |
audioInput |
Load/upload audio files | audio | audio |
glbViewer |
Load/display 3D GLB models | none | image |
output |
Display final result | image | none |
| Handle Type | Data Format | Description |
|---|---|---|
image |
Base64 data URL | Visual content |
text |
String | Text content |
audio |
Base64 data URL | Audio content |
- Type Matching: Handles only connect to matching types (
image→image,text→text) - Direction: Connections flow from source (output) to target (input)
- Multiplicity: Image inputs accept multiple connections; text inputs accept one
Returns { images: string[], text: string | null }.
Image data extracted from:
imageInput→data.imageannotation→data.outputImagenanoBanana→data.outputImage
Text data extracted from:
prompt→data.promptllmGenerate→data.outputText
Audio data extracted from:
audioInput→data.audioFilegenerateAudio→data.outputAudio
Cmd/Ctrl + Enter- Run workflowCmd/Ctrl + C/V- Copy/paste nodesShift + P- Add prompt node at centerShift + I- Add image input nodeShift + G- Add generate (nanoBanana) nodeShift + V- Add video (generateVideo) nodeShift + L- Add LLM nodeShift + A- Add annotation nodeShift + T- Add audio (generateAudio) nodeH- Stack selected nodes horizontallyV- Stack selected nodes verticallyG- Arrange selected nodes in grid?- Show keyboard shortcuts
- Define the data interface in
src/types/index.ts - Add to
NodeTypeunion insrc/types/index.ts - Create default data in
createDefaultNodeData()inworkflowStore.ts - Add dimensions to
defaultDimensionsinworkflowStore.ts - Create the component in
src/components/nodes/ - Export from
src/components/nodes/index.ts - Register in
nodeTypesinWorkflowCanvas.tsx - Add minimap color in
WorkflowCanvas.tsx - Update
getConnectedInputs()if the node produces consumable output - Add execution logic in
executeWorkflow()if the node requires processing - Update
ConnectionDropMenu.tsxto include the node in source/target lists
Use descriptive handle IDs matching the data type:
id="image"for image dataid="text"for text data
- Connection validation:
isValidConnection()inWorkflowCanvas.tsx - Workflow validation:
validateWorkflow()inworkflowStore.ts
Reference docs: https://docs.kie.ai/llms.txt lists all available model API pages.
Visit the model's doc page on https://docs.kie.ai/ and collect:
- Model ID(s) (the
modelparam sent to the API) - Capabilities: text-to-image, image-to-image, text-to-video, image-to-video
- API endpoint (standard:
/api/v1/jobs/createTask, or model-specific like Veo's/api/v1/veo/generate) - All input parameters: name, type, enum values, defaults, required status
- Image/video input parameter name (e.g.,
image_urls,imageUrls,input_urls) - Polling endpoint (standard:
/api/v1/jobs/recordInfo, or model-specific) - Response format and status field names
- Pricing (per-run cost if available)
File: src/app/api/models/route.ts — Add to KIE_MODELS array.
Each model entry needs: id, name, description, provider: "kie", capabilities, pricing, pageUrl.
Use separate entries for each capability variant (e.g., model/text-to-video and model/image-to-video).
File: src/app/api/models/[modelId]/route.ts — Add to getKieSchema().
Define parameters (user-configurable settings) and inputs (connectable handles like prompt, images).
File: src/app/api/generate/route.ts — Add case to getKieModelDefaults().
Provide required defaults that must be present even if the user doesn't set them.
File: src/app/api/generate/route.ts — Add to getKieImageInputKey().
Map the model to its correct image parameter name if it differs from the default image_urls.
If the model uses different endpoints than /api/v1/jobs/createTask and /api/v1/jobs/recordInfo:
- Add a detection function (e.g.,
isVeoModel()) - Add a model-ID-to-API-model mapping function
- Add a custom polling function for the model's status endpoint
- Add a branch in
generateWithKie()for the custom request format
All routes in src/app/api/:
| Route | Timeout | Purpose |
|---|---|---|
/api/generate |
5 min | Image generation via Gemini |
/api/llm |
1 min | Text generation (Google/OpenAI) |
/api/workflow |
default | Save/load workflow files |
/api/save-generation |
default | Auto-save generated images |
/api/logs |
default | Session logging |
node-banana-workflow-configs- Project metadata (paths)node-banana-workflow-costs- Cost tracking per workflownode-banana-nanoBanana-defaults- Sticky generation settings
- The primary development branch is
develop, NOTmainormaster - Always checkout
developbefore creating feature branches:git checkout develop - Create feature branches from
developusing:feature/<short-description>orfix/<short-description> - All PRs MUST target
develop: usegh pr create --base develop - Never push directly to
main,master, ordevelop
- Commit after each logical task or unit of work is complete. When implementing a multi-task plan, commit after finishing each task — do NOT batch all tasks into a single commit at the end.
- 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.