├── src ├── app │ ├── favicon.ico │ ├── layout.tsx │ ├── api │ │ └── copilotkit │ │ │ └── route.ts │ ├── globals.css │ └── page.tsx ├── lib │ ├── types.ts │ └── chat-input-context.tsx └── components │ ├── CustomChatInput.tsx │ ├── ApiKeyInput.tsx │ └── ArtifactPanel.tsx ├── agent ├── .gitignore ├── langgraph.json ├── requirements.txt ├── railway.json ├── .dockerignore ├── Dockerfile ├── server.py ├── pyproject.toml ├── DEPLOY.md └── agent.py ├── postcss.config.mjs ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.ts ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopilotKit/scene-creator-copilot/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /agent/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | *.pyc 4 | .env 5 | .vercel 6 | 7 | # python 8 | .venv/ 9 | .langgraph_api/ -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /agent/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "python_version": "3.12", 3 | "dockerfile_lines": [], 4 | "dependencies": ["."], 5 | "graphs": { 6 | "sample_agent": "./agent.py:graph" 7 | }, 8 | "env": ".env", 9 | "http": { 10 | "app": "./server.py:app" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /agent/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain>=0.3.28 2 | langgraph>=0.6.6 3 | langsmith>=0.4.23 4 | langchain-google-genai>=3.1.0 5 | langchain-core>=1.0.5 6 | httpx>=0.28.0 7 | fastapi>=0.115.5,<1.0.0 8 | uvicorn>=0.29.0,<1.0.0 9 | python-dotenv>=1.0.0,<2.0.0 10 | langgraph-cli[inmem]>=0.3.3 11 | -------------------------------------------------------------------------------- /agent/railway.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://railway.app/railway.schema.json", 3 | "build": { 4 | "builder": "DOCKERFILE", 5 | "dockerfilePath": "Dockerfile" 6 | }, 7 | "deploy": { 8 | "startCommand": "python -m langgraph_cli dev --port 8000 --host 0.0.0.0", 9 | "restartPolicyType": "ON_FAILURE", 10 | "restartPolicyMaxRetries": 10 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; 2 | import nextTypescript from "eslint-config-next/typescript"; 3 | 4 | const eslintConfig = [ 5 | ...nextCoreWebVitals, 6 | ...nextTypescript, 7 | { 8 | ignores: [ 9 | "node_modules/**", 10 | ".next/**", 11 | "out/**", 12 | "build/**", 13 | "next-env.d.ts", 14 | "agent", 15 | ], 16 | }, 17 | ]; 18 | 19 | export default eslintConfig; 20 | -------------------------------------------------------------------------------- /agent/.dockerignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | venv/ 8 | env/ 9 | ENV/ 10 | .venv 11 | pip-log.txt 12 | pip-delete-this-directory.txt 13 | 14 | # IDEs 15 | .vscode/ 16 | .idea/ 17 | *.swp 18 | *.swo 19 | *~ 20 | 21 | # OS 22 | .DS_Store 23 | Thumbs.db 24 | 25 | # Git 26 | .git/ 27 | .gitignore 28 | 29 | # Testing 30 | .pytest_cache/ 31 | .coverage 32 | htmlcov/ 33 | 34 | # Development 35 | .env.local 36 | .env.development 37 | *.log 38 | -------------------------------------------------------------------------------- /agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | WORKDIR /app 3 | 4 | # Install build dependencies and uv 5 | RUN apt-get update && apt-get install -y curl gcc && rm -rf /var/lib/apt/lists/* 6 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh 7 | ENV PATH="/root/.local/bin:${PATH}" 8 | 9 | # Copy dependency files 10 | COPY requirements.txt pyproject.toml /app/ 11 | 12 | # Install dependencies using uv 13 | RUN uv pip install --system -r requirements.txt 14 | 15 | # Copy application files 16 | COPY . /app 17 | 18 | # Expose port (Railway will use PORT env var) 19 | EXPOSE 8000 20 | 21 | # Start LangGraph dev server 22 | CMD ["python", "-m", "langgraph_cli", "dev", "--port", "8000", "--host", "0.0.0.0"] 23 | -------------------------------------------------------------------------------- /agent/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom routes to extend the LangGraph API server. 3 | 4 | This mounts a /generated route for serving generated images. 5 | Configure in langgraph.json via http.app setting. 6 | """ 7 | from pathlib import Path 8 | from fastapi import FastAPI 9 | from fastapi.staticfiles import StaticFiles 10 | 11 | # Create the generated images directory 12 | GENERATED_DIR = Path(__file__).parent / "generated" 13 | GENERATED_DIR.mkdir(exist_ok=True) 14 | 15 | # Custom FastAPI app to mount alongside LangGraph routes 16 | app = FastAPI(title="Custom Routes") 17 | 18 | # Mount static files for generated images 19 | app.mount("/generated", StaticFiles(directory=str(GENERATED_DIR)), name="generated") 20 | -------------------------------------------------------------------------------- /agent/pyproject.toml: -------------------------------------------------------------------------------- 1 | # Python dependencies for the Scene Creator agent 2 | # This file documents the project dependencies in a standard format 3 | # For installation, use: uv pip install -r requirements.txt 4 | 5 | [project] 6 | name = "scene-creator-agent" 7 | version = "0.1.0" 8 | description = "LangGraph agent for scene creation with Gemini 3 and Nano Banana" 9 | requires-python = ">=3.10" 10 | dependencies = [ 11 | "langchain>=0.3.28", 12 | "langgraph>=0.6.6", 13 | "langsmith>=0.4.23", 14 | "langchain-google-genai>=3.1.0", 15 | "langchain-core>=1.0.5", 16 | "httpx>=0.28.0", 17 | "fastapi>=0.115.5,<1.0.0", 18 | "uvicorn>=0.29.0,<1.0.0", 19 | "python-dotenv>=1.0.0,<2.0.0", 20 | "langgraph-cli[inmem]>=0.3.3", 21 | ] 22 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | // Types for scene generation artifacts 2 | 3 | export interface Character { 4 | id: string; 5 | name: string; 6 | description: string; 7 | imageUrl?: string; 8 | prompt?: string; 9 | } 10 | 11 | export interface Background { 12 | id: string; 13 | name: string; 14 | description: string; 15 | imageUrl?: string; 16 | prompt?: string; 17 | } 18 | 19 | export interface Scene { 20 | id: string; 21 | name: string; 22 | description: string; 23 | characterIds: string[]; 24 | backgroundId: string; 25 | imageUrl?: string; 26 | prompt?: string; 27 | } 28 | 29 | // Agent state matching Python AgentState 30 | export interface AgentState { 31 | characters: Character[]; 32 | backgrounds: Background[]; 33 | scenes: Scene[]; 34 | apiKey?: string; // Dynamic API key from frontend 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | # lock files 44 | package-lock.json 45 | yarn.lock 46 | pnpm-lock.yaml 47 | bun.lockb 48 | 49 | # LangGraph API 50 | .langgraph_api 51 | 52 | .claude 53 | .meridian -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "react-jsx", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./src/*" 28 | ] 29 | } 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts", 36 | ".next/dev/types/**/*.ts" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Space_Mono } from "next/font/google"; 3 | import { CopilotKit } from "@copilotkit/react-core"; 4 | import "./globals.css"; 5 | import "@copilotkit/react-ui/styles.css"; 6 | 7 | const spaceMono = Space_Mono({ 8 | weight: ["400", "700"], 9 | subsets: ["latin"], 10 | variable: "--font-space-mono", 11 | }); 12 | 13 | export const metadata: Metadata = { 14 | title: "Scene Creator - CopilotKit + Gemini 3 Demo", 15 | description: "Create scenes with AI-generated characters and backgrounds", 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return ( 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) Atai Barkai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib/chat-input-context.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, useContext, useState, useCallback, ReactNode } from "react"; 4 | 5 | interface ChatInputContextType { 6 | inputValue: string; 7 | setInputValue: (value: string) => void; 8 | inputRef: React.RefObject | null; 9 | setInputRef: (ref: React.RefObject) => void; 10 | } 11 | 12 | const ChatInputContext = createContext(null); 13 | 14 | export function ChatInputProvider({ children }: { children: ReactNode }) { 15 | const [inputValue, setInputValue] = useState(""); 16 | const [inputRef, setInputRef] = useState | null>(null); 17 | 18 | return ( 19 | 20 | {children} 21 | 22 | ); 23 | } 24 | 25 | export function useChatInput() { 26 | const context = useContext(ChatInputContext); 27 | if (!context) { 28 | throw new Error("useChatInput must be used within ChatInputProvider"); 29 | } 30 | return context; 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "langgraph-python-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run dev:ui\" \"npm run dev:agent\" --names ui,agent --prefix-colors blue,green --kill-others", 7 | "dev:debug": "LOG_LEVEL=debug npm run dev", 8 | "dev:agent": "cd agent && npx @langchain/langgraph-cli dev --port 8123 --no-browser", 9 | "dev:ui": "next dev --turbopack", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "eslint ." 13 | }, 14 | "dependencies": { 15 | "@ag-ui/langgraph": "0.0.18", 16 | "@copilotkit/react-core": "1.10.6", 17 | "@copilotkit/react-ui": "1.10.6", 18 | "@copilotkit/runtime": "1.10.6", 19 | "next": "16.0.1", 20 | "react": "^19.2.0", 21 | "react-dom": "^19.2.0", 22 | "zod": "^3.24.4" 23 | }, 24 | "devDependencies": { 25 | "@langchain/langgraph-cli": "0.0.40", 26 | "@tailwindcss/postcss": "^4", 27 | "@types/node": "^20", 28 | "@types/react": "^19", 29 | "@types/react-dom": "^19", 30 | "concurrently": "^9.1.2", 31 | "eslint": "^9", 32 | "eslint-config-next": "16.0.1", 33 | "tailwindcss": "^4", 34 | "typescript": "^5" 35 | } 36 | } -------------------------------------------------------------------------------- /src/app/api/copilotkit/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CopilotRuntime, 3 | ExperimentalEmptyAdapter, 4 | copilotRuntimeNextJSAppRouterEndpoint, 5 | } from "@copilotkit/runtime"; 6 | 7 | import { LangGraphAgent } from "@ag-ui/langgraph" 8 | import { NextRequest } from "next/server"; 9 | 10 | // 1. Use EmptyAdapter since we're in agent lock mode (LangGraph handles all LLM calls) 11 | // Suggestions will be set programmatically via useCopilotChatSuggestions with static values 12 | const serviceAdapter = new ExperimentalEmptyAdapter(); 13 | 14 | // 2. Create the CopilotRuntime instance and utilize the LangGraph AG-UI 15 | // integration to setup the connection. 16 | const runtime = new CopilotRuntime({ 17 | agents: { 18 | "sample_agent": new LangGraphAgent({ 19 | deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123", 20 | graphId: "sample_agent", 21 | langsmithApiKey: process.env.LANGSMITH_API_KEY || "", 22 | }), 23 | } 24 | }); 25 | 26 | // 3. Build a Next.js API route that handles the CopilotKit runtime requests. 27 | export const POST = async (req: NextRequest) => { 28 | const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ 29 | runtime, 30 | serviceAdapter, 31 | endpoint: "/api/copilotkit", 32 | }); 33 | 34 | return handleRequest(req); 35 | }; -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/CustomChatInput.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { type InputProps } from "@copilotkit/react-ui"; 4 | import { useChatInput } from "@/lib/chat-input-context"; 5 | import { useEffect, useRef } from "react"; 6 | 7 | export function CustomChatInput({ inProgress, onSend }: InputProps) { 8 | const { inputValue, setInputValue, setInputRef } = useChatInput(); 9 | const inputRef = useRef(null); 10 | 11 | // Register the input ref with context so other components can focus it 12 | useEffect(() => { 13 | setInputRef(inputRef as any); 14 | }, [setInputRef]); 15 | 16 | // Focus and move cursor to end when value changes externally 17 | useEffect(() => { 18 | if (inputValue && inputRef.current) { 19 | inputRef.current.focus(); 20 | // Move cursor to end 21 | const length = inputValue.length; 22 | inputRef.current.setSelectionRange(length, length); 23 | } 24 | }, [inputValue]); 25 | 26 | const handleSubmit = () => { 27 | if (inputValue.trim()) { 28 | onSend(inputValue); 29 | setInputValue(""); 30 | } 31 | }; 32 | 33 | const handleKeyDown = (e: React.KeyboardEvent) => { 34 | if (e.key === "Enter" && !e.shiftKey) { 35 | e.preventDefault(); 36 | handleSubmit(); 37 | } 38 | }; 39 | 40 | return ( 41 |
42 |