Repository: DahnM20/ai-flow Branch: main Commit: 98ebab6ff3f8 Files: 312 Total size: 1.6 MB Directory structure: gitextract_vbwzelad/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin/ │ └── generate_python_classes_from_ts.sh ├── docker/ │ ├── README.md │ ├── docker-compose.it.yml │ ├── docker-compose.yml │ └── healthcheck.sh ├── integration_tests/ │ ├── .gitignore │ ├── package.json │ ├── tests/ │ │ ├── nodeProcessingOrder/ │ │ │ ├── nodeErrorTest.ts │ │ │ ├── nodeParallelExecutionDurationTest.ts │ │ │ ├── nodeWithChildrenTest.ts │ │ │ ├── nodeWithMultipleParentsTest.ts │ │ │ ├── nodesWithoutLinkTest.ts │ │ │ └── singleNodeTest.ts │ │ └── socketEvents/ │ │ ├── processFileEventTest.ts │ │ ├── runNodeEventTest.ts │ │ └── socketConnectionTest.ts │ ├── tsconfig.json │ └── utils/ │ ├── requestDatas.ts │ └── testHooks.ts └── packages/ ├── backend/ │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── app/ │ │ ├── env_config.py │ │ ├── flask/ │ │ │ ├── app_routes/ │ │ │ │ ├── __init__.py │ │ │ │ ├── image_routes.py │ │ │ │ ├── node_routes.py │ │ │ │ ├── parameters_routes.py │ │ │ │ ├── static_routes.py │ │ │ │ └── upload_routes.py │ │ │ ├── decorators.py │ │ │ ├── flask_app.py │ │ │ ├── routes.py │ │ │ ├── socketio_init.py │ │ │ ├── sockets.py │ │ │ └── utils/ │ │ │ └── constants.py │ │ ├── llms/ │ │ │ └── utils/ │ │ │ └── max_token_for_model.py │ │ ├── log_config.py │ │ ├── processors/ │ │ │ ├── components/ │ │ │ │ ├── __init__.py │ │ │ │ ├── core/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── ai_data_splitter_processor.py │ │ │ │ │ ├── dall_e_prompt_processor.py │ │ │ │ │ ├── display_processor.py │ │ │ │ │ ├── file_processor.py │ │ │ │ │ ├── gpt_vision_processor.py │ │ │ │ │ ├── input_image_processor.py │ │ │ │ │ ├── input_processor.py │ │ │ │ │ ├── llm_prompt_processor.py │ │ │ │ │ ├── merge_processor.py │ │ │ │ │ ├── processor_type_name_utils.py │ │ │ │ │ ├── replicate_processor.py │ │ │ │ │ ├── stable_diffusion_stabilityai_prompt_processor.py │ │ │ │ │ ├── stable_video_diffusion_replicate.py │ │ │ │ │ ├── transition_processor.py │ │ │ │ │ ├── url_input_processor.py │ │ │ │ │ └── youtube_transcript_input_processor.py │ │ │ │ ├── extension/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── claude_anthropic_processor.py │ │ │ │ │ ├── deepseek_processor.py │ │ │ │ │ ├── document_to_text_processor.py │ │ │ │ │ ├── extension_processor.py │ │ │ │ │ ├── generate_number_processor.py │ │ │ │ │ ├── gpt_image_processor.py │ │ │ │ │ ├── http_get_processor.py │ │ │ │ │ ├── open_router_processor.py │ │ │ │ │ ├── openai_reasoning_processor.py │ │ │ │ │ ├── openai_text_to_speech_processor.py │ │ │ │ │ ├── replace_text_processor.py │ │ │ │ │ ├── stabilityai_generic_processor.py │ │ │ │ │ └── stable_diffusion_three_processor.py │ │ │ │ ├── model.py │ │ │ │ ├── node_config_builder.py │ │ │ │ ├── node_config_utils.py │ │ │ │ └── processor.py │ │ │ ├── context/ │ │ │ │ ├── processor_context.py │ │ │ │ └── processor_context_flask_request.py │ │ │ ├── exceptions.py │ │ │ ├── factory/ │ │ │ │ ├── processor_factory.py │ │ │ │ └── processor_factory_iter_modules.py │ │ │ ├── launcher/ │ │ │ │ ├── abstract_topological_processor_launcher.py │ │ │ │ ├── async_processor_launcher.py │ │ │ │ ├── basic_processor_launcher.py │ │ │ │ ├── event_type.py │ │ │ │ ├── processor_event.py │ │ │ │ ├── processor_launcher.py │ │ │ │ └── processor_launcher_event.py │ │ │ ├── observer/ │ │ │ │ ├── observer.py │ │ │ │ └── socketio_event_emitter.py │ │ │ └── utils/ │ │ │ └── retry_mixin.py │ │ ├── root_injector.py │ │ ├── storage/ │ │ │ ├── local_storage_strategy.py │ │ │ ├── s3_storage_strategy.py │ │ │ └── storage_strategy.py │ │ ├── tasks/ │ │ │ ├── green_pool_task_manager.py │ │ │ ├── single_thread_tasks/ │ │ │ │ └── browser/ │ │ │ │ ├── async_browser_task.py │ │ │ │ └── browser_task.py │ │ │ ├── task_exception.py │ │ │ ├── task_manager.py │ │ │ ├── task_utils.py │ │ │ └── thread_pool_task_manager.py │ │ └── utils/ │ │ ├── node_extension_utils.py │ │ ├── openapi_client.py │ │ ├── openapi_converter.py │ │ ├── openapi_reader.py │ │ ├── processor_utils.py │ │ ├── replicate_utils.py │ │ └── web_scrapping/ │ │ ├── async_browser_manager.py │ │ └── browser_manager.py │ ├── config.yaml │ ├── hooks/ │ │ └── hook-app.processors.py │ ├── pyproject.toml │ ├── requirements_windows.txt │ ├── resources/ │ │ ├── data/ │ │ │ └── openrouter_models.json │ │ └── openapi/ │ │ └── stabilityai.json │ ├── server.py │ └── tests/ │ ├── unit/ │ │ ├── test_processor_factory.py │ │ ├── test_processor_launcher.py │ │ └── test_stable_diffusion_stabilityai_prompt_processor.py │ └── utils/ │ ├── openai_mock_utils.py │ ├── processor_context_mock.py │ └── processor_factory_mock.py └── ui/ ├── .gitignore ├── .prettierignore ├── Dockerfile ├── README.md ├── index.html ├── jest.config.ts ├── nginx.conf ├── package.json ├── postcss.config.cjs ├── postcss.config.js ├── prettier.config.js ├── public/ │ ├── health │ ├── locales/ │ │ ├── en/ │ │ │ ├── aiActions.json │ │ │ ├── config.json │ │ │ ├── dialogs.json │ │ │ ├── flow.json │ │ │ ├── nodeHelp.json │ │ │ ├── tips.json │ │ │ ├── tour.json │ │ │ └── version.json │ │ └── fr/ │ │ ├── aiActions.json │ │ ├── config.json │ │ ├── dialogs.json │ │ ├── flow.json │ │ ├── nodeHelp.json │ │ ├── tips.json │ │ ├── tour.json │ │ └── version.json │ ├── robots.txt │ ├── samples/ │ │ └── intro.json │ └── site.webmanifest ├── src/ │ ├── App.tsx │ ├── Main.tsx │ ├── api/ │ │ ├── cache/ │ │ │ ├── cacheManager.ts │ │ │ └── withCache.ts │ │ ├── client.ts │ │ ├── nodes.ts │ │ ├── parameters.ts │ │ ├── replicateModels.ts │ │ └── uploadFile.ts │ ├── components/ │ │ ├── Flow.tsx │ │ ├── LoadingScreen.tsx │ │ ├── bars/ │ │ │ ├── Sidebar.tsx │ │ │ └── dnd-sidebar/ │ │ │ ├── DnDSidebar.tsx │ │ │ ├── DraggableNode.tsx │ │ │ ├── DraggableNodeWithSubnodes.tsx │ │ │ ├── GripIcon.tsx │ │ │ ├── Section.tsx │ │ │ └── types.ts │ │ ├── buttons/ │ │ │ ├── ButtonRunAll.tsx │ │ │ └── ConfigurationButton.tsx │ │ ├── edges/ │ │ │ └── buttonEdge.tsx │ │ ├── handles/ │ │ │ └── HandleWrapper.tsx │ │ ├── inputs/ │ │ │ └── InputWithButton.tsx │ │ ├── nodes/ │ │ │ ├── AIDataSplitterNode.tsx │ │ │ ├── DisplayNode.tsx │ │ │ ├── FileUploadNode.tsx │ │ │ ├── GenericNode.tsx │ │ │ ├── Node.styles.ts │ │ │ ├── NodeHelpPopover.tsx │ │ │ ├── NodeWrapper.tsx │ │ │ ├── ReplicateNode.tsx │ │ │ ├── TransitionNode.tsx │ │ │ ├── node-button/ │ │ │ │ ├── InputNameBar.tsx │ │ │ │ └── NodePlayButton.tsx │ │ │ ├── node-input/ │ │ │ │ ├── FileUploadField.tsx │ │ │ │ ├── ImageMaskCreator.tsx │ │ │ │ ├── ImageMaskCreatorField.tsx │ │ │ │ ├── ImageMaskCreatorFieldFlowAware.tsx │ │ │ │ ├── KeyValueInputList.tsx │ │ │ │ ├── NodeField.tsx │ │ │ │ ├── NodeTextField.tsx │ │ │ │ ├── NodeTextarea.tsx │ │ │ │ ├── OutputRenderer.tsx │ │ │ │ └── TextAreaPopupWrapper.tsx │ │ │ ├── node-output/ │ │ │ │ ├── AudioUrlOutput.tsx │ │ │ │ ├── ImageBase64Output.tsx │ │ │ │ ├── ImageUrlOutput.tsx │ │ │ │ ├── MarkdownOutput.tsx │ │ │ │ ├── NodeOutput.tsx │ │ │ │ ├── OutputDisplay.tsx │ │ │ │ ├── PdfUrlOutput.tsx │ │ │ │ ├── ThreeDimensionalUrlOutput.tsx │ │ │ │ ├── VideoUrlOutput.tsx │ │ │ │ └── outputUtils.ts │ │ │ ├── types/ │ │ │ │ └── node.ts │ │ │ └── utils/ │ │ │ ├── HintComponent.tsx │ │ │ ├── ImageModal.tsx │ │ │ ├── ImageZoomable.tsx │ │ │ ├── NodeHelp.tsx │ │ │ ├── NodeIcons.tsx │ │ │ └── TextareaModal.tsx │ │ ├── players/ │ │ │ └── VideoJS.tsx │ │ ├── popups/ │ │ │ ├── ConfirmPopup.tsx │ │ │ ├── DefaultPopup.tsx │ │ │ ├── HelpPopup.tsx │ │ │ ├── UserMessagePopup.tsx │ │ │ ├── config-popup/ │ │ │ │ ├── AppParameters.tsx │ │ │ │ ├── ConfigPopup.tsx │ │ │ │ ├── DisplayParameters.tsx │ │ │ │ ├── ParametersFields.tsx │ │ │ │ ├── UserParameters.tsx │ │ │ │ ├── configMetadata.ts │ │ │ │ └── parameters.ts │ │ │ ├── select-model-popup/ │ │ │ │ ├── Model.tsx │ │ │ │ └── SelectModelPopup.tsx │ │ │ └── shared/ │ │ │ ├── FilterGrid.tsx │ │ │ ├── Grid.tsx │ │ │ └── LoadMoreButton.tsx │ │ ├── selectors/ │ │ │ ├── ActionGroup.tsx │ │ │ ├── ColorSelector.tsx │ │ │ ├── ExpandableBloc.tsx │ │ │ ├── FileDropZone.tsx │ │ │ ├── OptionSelector.tsx │ │ │ └── SelectAutocomplete.tsx │ │ ├── shared/ │ │ │ ├── motions/ │ │ │ │ ├── EaseOut.tsx │ │ │ │ ├── TapScale.tsx │ │ │ │ └── types.ts │ │ │ └── theme.tsx │ │ ├── side-views/ │ │ │ ├── CurrentNodeView.tsx │ │ │ └── JSONView.tsx │ │ ├── tools/ │ │ │ └── Fallback.tsx │ │ └── tour/ │ │ └── AppTour.tsx │ ├── config/ │ │ └── config.ts │ ├── hooks/ │ │ ├── useFlowSocketListeners.tsx │ │ ├── useFormFields.tsx │ │ ├── useHandlePositions.tsx │ │ ├── useHandleShowOutput.tsx │ │ ├── useIsPlaying.tsx │ │ ├── useIsTouchDevice.tsx │ │ ├── useLoading.tsx │ │ ├── useLocalStorage.tsx │ │ └── useRefreshOnAppearanceChange.tsx │ ├── i18n.js │ ├── index.css │ ├── index.tsx │ ├── init.js │ ├── layout/ │ │ └── main-layout/ │ │ ├── AppLayout.tsx │ │ ├── header/ │ │ │ ├── Tab.tsx │ │ │ └── TabHeader.tsx │ │ └── wrapper/ │ │ ├── FlowErrorBoundary.tsx │ │ └── FlowWrapper.tsx │ ├── nodes-configuration/ │ │ ├── dallENode.ts │ │ ├── gptVisionNode.ts │ │ ├── inputTextNode.ts │ │ ├── llmPrompt.ts │ │ ├── mergerPromptNode.ts │ │ ├── nodeConfig.ts │ │ ├── sectionConfig.ts │ │ ├── stableDiffusionStabilityAiNode.ts │ │ ├── types.ts │ │ ├── urlNode.ts │ │ └── youtubeTranscriptNode.ts │ ├── providers/ │ │ ├── FlowDataProvider.tsx │ │ ├── NodeProvider.tsx │ │ ├── SocketProvider.tsx │ │ ├── ThemeProvider.tsx │ │ └── VisibilityProvider.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── services/ │ │ └── tabStorage.ts │ ├── setupTests.ts │ ├── sockets/ │ │ ├── flowEventTypes.ts │ │ └── flowSocket.ts │ ├── utils/ │ │ ├── evaluateConditions.ts │ │ ├── flowChecker.ts │ │ ├── flowUtils.ts │ │ ├── mappings.tsx │ │ ├── navigatorUtils.ts │ │ ├── nodeConfigurationUtils.ts │ │ ├── nodeUtils.ts │ │ ├── openAPIUtils.ts │ │ └── toastUtils.tsx │ └── vite-env.d.ts ├── tailwind.config.js ├── test/ │ ├── e2e/ │ │ ├── intro-flow.spec.ts │ │ ├── loading-screen.spec.ts │ │ ├── main-content.spec.ts │ │ ├── sidebar-default-nodes.spec.ts │ │ ├── sidebar-extensions-nodes.spec.ts │ │ └── tuto-display.spec.ts │ ├── unit/ │ │ ├── flowChecker.test.ts │ │ └── flowUtils.test.ts │ └── utils.ts ├── tsconfig.json ├── vite.config.ts └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [DahnM20] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/workflows/main.yml ================================================ name: Docker Compose Build | Healthcheck | Tests on: push: branches: - main - develop - develop-features-0.8.1 jobs: build_and_test: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - name: Move to docker directory and run docker compose run: | cd docker docker compose -f docker-compose.it.yml up -d - name: Run healthcheck script run: | cd docker chmod +x healthcheck.sh ./healthcheck.sh http://localhost:5000/healthcheck - name: Print Docker logs if: failure() run: | cd docker docker compose logs - name: Run UI unit tests run: | cd packages/ui npm i npm run test - name: Run Python unit tests run: | docker exec ai-flow-backend python -m unittest discover -s tests/unit -p '*test_*.py' - name: Run integration tests run: | cd integration_tests npm i npm run test - name: Print Docker logs if: failure() run: | cd docker docker compose logs ================================================ FILE: .gitignore ================================================ packages/backend/.env ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Dahn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

AI-Flow Logo

Open-source tool to seamlessly connect multiple AI model APIs into repeatable workflows.

English French Follow on Twitter

🔗 Website📚 Documentation

---
🎉🚀 Latest Release: v0.11.3 🚀🎉
Nodes Updated : Web search can be enabled on GPT node, Claude 4 available
UI : Node Search Bar, Shortcut for Popular Replicate Models
New Models available : Flux Kontext, Veo 3, Lyria 2, Imagen 4 available through the Replicate Node
--- ![AI-Flow Intro](assets/flow-example-3.png) ## Overview **AI-Flow** is an open-source, user-friendly UI that lets you visually design, manage, and monitor AI-driven workflows by seamlessly connecting multiple AI model APIs (e.g., OpenAI, StabilityAI, Replicate, Claude, Deepseek). ## Features - **Visual Workflow Builder:** Drag-and-drop interface for crafting AI workflows. - **Real-Time Monitoring:** Watch your workflow execute and track results. - **Parallel Processing:** Nodes run in parallel whenever possible. - **Model Management:** Easily organize and manage diverse AI models. - **Import/Export:** Share or back up your workflows effortlessly. ## Supported Models - **Replicate:** All models available through the Replicate API (FLUX.1, FLUX.1 Kontext, Imagen 4, Veo 3, Lyria 2, and many more) - **OpenAI:** GPT-4o, GPT-4.1, TTS, o1, o3, o4. - **StabilityAI:** Stable Diffusion 3.5, SDXL, Stable Video Diffusion, plus additional tools. - **Others:** Claude, Deepseek, OpenRouter. ![Scenario Example](assets/flow-example-2.png) ## Open Source vs. Cloud **AI-Flow** is fully open source and available under the MIT License, empowering you to build and run your AI workflows on your personal machine. For those seeking enhanced functionality and a polished experience, **AI-Flow Pro** on our cloud platform ([app.ai-flow.net](https://ai-flow.net/?ref=github)) offers advanced features, including: - **Subflows & Loops:** Create complex, nested workflows and iterate tasks effortlessly. - **API-Triggered Flows:** Initiate workflows via API calls for seamless automation. - **Integrated Services:** Connect with external services such as Google Search, Airtable, Zapier, and Make. - **Simplified Interface:** Transform workflows into streamlined tools with an intuitive UI. ![Pro VS Open Source](assets/comparison-pro-vs-opensource-v2.png) The cloud version builds upon the foundation of the open-source project, giving you more power and flexibility while still letting you use your own API keys. ## Installation > **Note:** To unlock full functionality, AI-Flow requires S3-compatible storage (with proper CORS settings) to host resources. Without it, features like File Upload or nodes that rely on external providers (e.g., StabilityAI) may not work as expected. Also, set `REPLICATE_API_KEY` in the App Parameters or in your environment to use the Replicate node. ### Method 1: Using the Executable (Windows Only) > **Note:** This method is only available for Windows users. 1. Download the latest Windows version of AI-Flow from the official releases page: [AI-Flow Releases](https://ai-flow.net/release/) 2. Once downloaded, run the `.exe` file. This will start a local server and open AI-Flow in a standalone window, giving you direct access to its user interface without needing to install anything else. ### Method 2 : Docker Installation 1. **Prepare Docker Compose:** - Navigate to the `docker` directory: ```bash cd docker ``` 2. **Launch with Docker Compose:** ```bash docker-compose up -d ``` 3. **Access the Application:** - Open [http://localhost:80](http://localhost:80) in your browser. - To stop, run: ```bash docker-compose stop ``` ### Method 3 : Local Installation 1. **Clone the Repository:** ```bash git clone https://github.com/DahnM20/ai-flow.git cd ai-flow ``` 2. **UI Setup:** ```bash cd packages/ui npm install ``` 3. **Backend Setup:** ```bash cd ../backend poetry install ``` - **Windows Users:** ```bash poetry shell pip install -r requirements_windows.txt ``` 4. **Run the Application:** - Start the backend: ```bash poetry run python server.py ``` - In a new terminal, start the UI: ```bash cd packages/ui npm start ``` - Open your browser and navigate to [http://localhost:3000](http://localhost:3000). ## Contributing We welcome contributions! If you encounter issues or have feature ideas, please [open an issue](https://github.com/DahnM20/ai-flow/issues) or submit a pull request. ## License This project is released under the [MIT License](LICENSE). ================================================ FILE: bin/generate_python_classes_from_ts.sh ================================================ npm i -g typescript-json-schema typescript-json-schema "../packages/ui/src/nodes-configuration/types.ts" "*" --out "schema.json" mv schema.json ../packages/backend/app/processors/components/ cd ../packages/backend/app/processors/components/ poetry run datamodel-codegen --input schema.json --input-file-type jsonschema --output model.py --output-model-type pydantic_v2.BaseModel --enum-field-as-literal all rm schema.json echo "model.py generated" ================================================ FILE: docker/README.md ================================================ ## 🐳 Docker ### Docker Compose 1. Go to the docker directory: `cd ./docker` 2. Update the .yml if needed for the PORTS 3. Launch `docker-compose up` or `docker-compose up -d` 4. Open your browser and navigate to `http://localhost:3000` 5. Use `docker-compose stop` when you want to stop the app. ================================================ FILE: docker/docker-compose.it.yml ================================================ services: backend: container_name: ai-flow-backend build: context: ../packages/backend/ dockerfile: Dockerfile ports: - 5000:5000 environment: - HOST=0.0.0.0 - PORT=5000 - DEPLOYMENT_ENV=LOCAL - LOCAL_STORAGE_FOLDER_NAME=local_storage - USE_MOCK=true volumes: - ./ai-flow-backend-storage:/app/local_storage frontend: container_name: ai-flow-frontend build: context: ../packages/ui/ dockerfile: Dockerfile ports: - 80:80 environment: - VITE_APP_WS_HOST=localhost - VITE_APP_WS_PORT=5000 ================================================ FILE: docker/docker-compose.yml ================================================ services: backend: container_name: ai-flow-backend build: context: ../packages/backend/ dockerfile: Dockerfile ports: - 5001:5000 environment: - HOST=0.0.0.0 - PORT=5000 - DEPLOYMENT_ENV=LOCAL - REPLICATE_API_KEY=sample - LOCAL_STORAGE_FOLDER_NAME=local_storage volumes: - ./ai-flow-backend-storage:/app/local_storage frontend: container_name: ai-flow-frontend build: context: ../packages/ui/ dockerfile: Dockerfile args: VITE_APP_WS_HOST: localhost VITE_APP_WS_PORT: 5001 VITE_APP_API_REST_PORT: 5001 ports: - 80:80 ================================================ FILE: docker/healthcheck.sh ================================================ #!/bin/bash if [ "$#" -ne 1 ]; then echo "Usage: $0 " exit 1 fi URL="$1" INTERVAL=5 MAX_ATTEMPTS=20 attempt=0 while [ $attempt -lt $MAX_ATTEMPTS ]; do attempt=$(( $attempt + 1 )) curl --fail --silent $URL && echo "Service is up!" && exit 0 echo "Service not ready yet. Waiting for $INTERVAL seconds. Attempt $attempt of $MAX_ATTEMPTS." sleep $INTERVAL done echo "Service did not become ready after $MAX_ATTEMPTS attempts." exit 1 ================================================ FILE: integration_tests/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /dist # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: integration_tests/package.json ================================================ { "name": "integration_tests", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "test": "mocha dist/tests/**/*Test.js", "build": "tsc", "pretest": "npm run build" }, "author": "", "license": "ISC", "dependencies": { "axios": "^1.5.0", "chai": "^4.3.8", "mocha": "^10.2.0", "socket.io-client": "^4.7.2" }, "devDependencies": { "@types/chai": "^4.3.6", "@types/minimist": "^1.2.2", "@types/mocha": "^10.0.1", "@types/node": "^20.5.9", "@types/normalize-package-data": "^2.4.1", "@types/socket.io-client": "^3.0.0", "typescript": "^5.2.2" } } ================================================ FILE: integration_tests/tests/nodeProcessingOrder/nodeErrorTest.ts ================================================ import { expect } from "chai"; import { disconnectSocket, getSocket, setupSocket, } from "../../utils/testHooks"; import { createRequestData, flowWithFourParallelNodeStep, flowWithoutLinks, sequentialFlow, } from "../../utils/requestDatas"; describe("Node errors test", function () { this.timeout(30000); beforeEach(function (done) { setupSocket(done); }); afterEach(function () { disconnectSocket(); }); it("Error in sequential flow should stop the flow", function (done) { const socket = getSocket(); const flowNodesWithOneError = structuredClone(sequentialFlow); flowNodesWithOneError[1] = { ...flowNodesWithOneError[1], raiseError: true, }; socket.emit("process_file", createRequestData(flowNodesWithOneError)); let errorReceived = false; let progressCount = 0; const progressBeforeError = 1; socket.on("error", (error) => { errorReceived = true; expect(progressCount).to.equal(progressBeforeError); }); socket.on("progress", (progress) => { if (errorReceived) { done(new Error("Received progress after error")); } progressCount++; if (progressCount > progressBeforeError) { done(new Error(`Too many nodes sent progress`)); } }); setTimeout(() => { if (!errorReceived) { socket.disconnect(); done(new Error("No error received within the expected time")); } else { done(); } }, 10000); }); it("Error in flow without link should run the others nodes", function (done) { const socket = getSocket(); const flow = structuredClone(sequentialFlow); flow[1] = { ...flow[1], raiseError: true, }; socket.emit("process_file", createRequestData(flow)); let errorReceived = false; let progressCount = 0; const maxProgressBeforeError = 2; socket.on("error", (error) => { errorReceived = true; expect(progressCount).to.be.at.most(maxProgressBeforeError); }); socket.on("progress", (progress) => { progressCount++; if (progressCount > maxProgressBeforeError) { done(new Error(`Too many nodes sent progress`)); } }); setTimeout(() => { if (errorReceived && progressCount === 1) { socket.disconnect(); done(); } else { done(new Error("No error received within the expected time")); } }, 2000); }); it("Error in flow with 4 parallel node should return result for all of them except the one in error", function (done) { const socket = getSocket(); const flow = structuredClone(flowWithFourParallelNodeStep); flow[4] = { ...flow[4], raiseError: true, }; flow[1] = { ...flow[1], sleepDuration: 2, }; //One node with high delay to be sure he is processed during the error raise socket.emit("process_file", createRequestData(flow)); let errorReceived = false; let progressCount = 0; const maxProgressBeforeError = 4; socket.on("error", (error) => { errorReceived = true; expect(progressCount).to.be.at.most(maxProgressBeforeError); }); socket.on("progress", (progress) => { progressCount++; if (progressCount > maxProgressBeforeError) { done(new Error(`Too many nodes sent progress`)); } }); socket.on("run_end", (end) => { if (progressCount !== maxProgressBeforeError) { done( new Error(`Not all nodes were processed before end of execution.`) ); } else { done(); } }); }); }); ================================================ FILE: integration_tests/tests/nodeProcessingOrder/nodeParallelExecutionDurationTest.ts ================================================ import { expect } from "chai"; import { disconnectSocket, getSocket, setupSocket, } from "../../utils/testHooks"; import { createRequestData, flowWithFourParallelNodeStep, } from "../../utils/requestDatas"; describe("Node errors test", function () { this.timeout(15000); beforeEach(function (done) { setupSocket(done); }); afterEach(function () { disconnectSocket(); }); it("4 parallel node with 2s sleep each should not compound time", function (done) { const socket = getSocket(); const flow = structuredClone(flowWithFourParallelNodeStep); flow[1] = { ...flow[1], sleepDuration: 2, }; flow[2] = { ...flow[2], sleepDuration: 2, }; flow[3] = { ...flow[3], sleepDuration: 2, }; flow[4] = { ...flow[4], sleepDuration: 2, }; const maxDurationMsExpected = 5000; const timeStart = Date.now(); socket.emit("process_file", createRequestData(flow)); let progressCount = 0; const maxProgress = 5; socket.on("progress", (progress) => { progressCount++; if (progressCount > maxProgress) { done(new Error(`Too many nodes sent progress`)); } }); socket.on("run_end", (end) => { if (progressCount !== maxProgress) { done( new Error(`Not all nodes were processed before end of execution.`) ); } else { const timeEnd = Date.now(); const duration = timeEnd - timeStart; expect(duration).to.be.lessThan(maxDurationMsExpected); done(); } }); }); }); ================================================ FILE: integration_tests/tests/nodeProcessingOrder/nodeWithChildrenTest.ts ================================================ import { expect } from "chai"; import { disconnectSocket, getSocket, setupSocket } from "../../utils/testHooks"; import { createRequestData } from "../../utils/requestDatas"; describe('node with children test', function () { this.timeout(15000); beforeEach(function (done) { setupSocket(done); }); afterEach(function () { disconnectSocket(); }); const flowNodeWithChildren = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", }, { inputs: [ { "inputNode": "1#llm-prompt", "inputNodeOutputKey": 0 } ], name: "2#llm-prompt", processorType: "llm-prompt", }, { inputs: [ { "inputNode": "1#llm-prompt", "inputNodeOutputKey": 0 } ], name: "3#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", } ]; it('process_file should process the parent first, then its children', function (done) { const socket = getSocket(); socket.emit('process_file', createRequestData(flowNodeWithChildren)); let processedNodes: string[] = []; socket.on('progress', (data) => { processedNodes.push(data.instanceName); if (processedNodes.length === flowNodeWithChildren.length) { try { //First one needs to be the parent expect(processedNodes[0]).to.equal(flowNodeWithChildren[0].name); expect(processedNodes).to.includes(flowNodeWithChildren[1].name); expect(processedNodes).to.includes(flowNodeWithChildren[2].name); done(); } catch (error) { done(error); } } }); socket.on('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); }); ================================================ FILE: integration_tests/tests/nodeProcessingOrder/nodeWithMultipleParentsTest.ts ================================================ import { expect } from "chai"; import { disconnectSocket, getSocket, setupSocket } from "../../utils/testHooks"; import { createRequestData } from "../../utils/requestDatas"; describe('node with multiple parent test', function () { this.timeout(15000); beforeEach(function (done) { setupSocket(done); }); afterEach(function () { disconnectSocket(); }); const flowWithNodesWithMultipleParents = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", }, { inputs: [], name: "2#llm-prompt", processorType: "llm-prompt", }, { inputs: [ { "inputNode": "1#llm-prompt", "inputNodeOutputKey": 0 }, { "inputNode": "2#llm-prompt", "inputNodeOutputKey": 0 } ], name: "3#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", } ]; it('process_file should process both parents before the child', function (done) { const socket = getSocket(); socket.emit('process_file', createRequestData(flowWithNodesWithMultipleParents)); let processedNodes: string[] = []; socket.on('progress', (data) => { processedNodes.push(data.instanceName); if (processedNodes.length === flowWithNodesWithMultipleParents.length) { try { // Check if both parents are processed before the child expect(processedNodes.includes("1#llm-prompt")).to.be.true; expect(processedNodes.includes("2#llm-prompt")).to.be.true; expect(processedNodes.indexOf("3#stable-diffusion-stabilityai-prompt")).to.be.greaterThan(processedNodes.indexOf("1#llm-prompt")); expect(processedNodes.indexOf("3#stable-diffusion-stabilityai-prompt")).to.be.greaterThan(processedNodes.indexOf("2#llm-prompt")); done(); } catch (error) { done(error); } } }); socket.on('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); }); ================================================ FILE: integration_tests/tests/nodeProcessingOrder/nodesWithoutLinkTest.ts ================================================ import { expect } from "chai"; import { disconnectSocket, getSocket, setupSocket } from "../../utils/testHooks"; import { createRequestData } from "../../utils/requestDatas"; describe('node without link test', function () { this.timeout(15000); beforeEach(function (done) { setupSocket(done); }); afterEach(function () { disconnectSocket(); }); const flowWithNodesWithoutLink = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", }, { inputs: [], name: "2#llm-prompt", processorType: "llm-prompt", }, { inputs: [], name: "3#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", } ]; it('process_file should process all the nodes', function (done) { const socket = getSocket(); socket.emit('process_file', createRequestData(flowWithNodesWithoutLink)); let processedNodes: string[] = []; socket.on('progress', (data) => { processedNodes.push(data.instanceName); if (processedNodes.length === flowWithNodesWithoutLink.length) { try { expect(processedNodes).to.includes(flowWithNodesWithoutLink[0].name); expect(processedNodes).to.includes(flowWithNodesWithoutLink[1].name); expect(processedNodes).to.includes(flowWithNodesWithoutLink[2].name); done(); } catch (error) { done(error); } } }); socket.on('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); }); ================================================ FILE: integration_tests/tests/nodeProcessingOrder/singleNodeTest.ts ================================================ import { expect } from "chai"; import { Socket, io } from "socket.io-client"; import { createRequestData } from "../../utils/requestDatas"; describe('single node test', function () { this.timeout(5000); let socket: Socket; beforeEach(function (done) { socket = io('http://localhost:5000'); socket.on('connect', function () { done(); }); socket.on('connect_error', function (error) { done(error); }); }); afterEach(function () { socket.disconnect(); }); const flowWithSingleNode = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", } ]; it('process_file should trigger one progress event', function (done) { socket.emit('process_file', createRequestData(flowWithSingleNode)); socket.once('progress', (data) => { expect(data).to.have.property('instanceName').to.equal(flowWithSingleNode[0].name); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); }); ================================================ FILE: integration_tests/tests/socketEvents/processFileEventTest.ts ================================================ import { io, Socket } from "socket.io-client"; import { expect } from 'chai'; import { basicJsonFlow, getBasicProcessFileData, getJsonFlowWithMissingInputTextProcessFileData } from '../../utils/requestDatas'; describe('process_file event tests', function () { this.timeout(5000); let socket: Socket; beforeEach(function (done) { socket = io('http://localhost:5000'); socket.on('connect', function () { done(); }); socket.on('connect_error', function (error) { done(error); }); }); afterEach(function () { socket.disconnect(); }); it('process_file should trigger run_end event', function (done) { const processFileData = getBasicProcessFileData(); socket.emit('process_file', processFileData); socket.once('run_end', (data) => { expect(data).to.have.property('output'); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); it('process_file should trigger progress event', function (done) { const processFileData = getBasicProcessFileData(); socket.emit('process_file', processFileData); socket.once('progress', (data) => { expect(data).to.have.property('output').to.equal(basicJsonFlow[0].inputText); expect(data).to.have.property('instanceName').to.equal(basicJsonFlow[0].name); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); it('process_file should trigger current_node_running event', function (done) { const processFileData = getBasicProcessFileData(); socket.emit('process_file', processFileData); socket.once('current_node_running', (data) => { expect(data).to.have.property('instanceName').to.equal(basicJsonFlow[0].name); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); it('process_file with missing input should trigger error event', function (done) { const processFileData = getJsonFlowWithMissingInputTextProcessFileData(); socket.emit('process_file', processFileData); socket.once('error', (error) => { done(); }); }); }); ================================================ FILE: integration_tests/tests/socketEvents/runNodeEventTest.ts ================================================ import { io, Socket } from "socket.io-client"; import { expect } from 'chai'; import { basicJsonFlow, getBasicRunNodeData, getJsonFlowWithMissingInputTextProcessFileData } from '../../utils/requestDatas'; describe('run_node event tests', function () { this.timeout(5000); let socket: Socket; beforeEach(function (done) { socket = io('http://localhost:5000'); socket.on('connect', function () { done(); }); socket.on('connect_error', function (error) { done(error); }); }); afterEach(function () { socket.disconnect(); }); it('run_node should trigger progress event', function (done) { const runNodeData = getBasicRunNodeData(); socket.emit('run_node', runNodeData); socket.once('progress', (data) => { expect(data).to.have.property('output').to.equal(basicJsonFlow[0].inputText); expect(data).to.have.property('instanceName').to.equal(basicJsonFlow[0].name); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); it('run_node should trigger current_node_running event', function (done) { const runNodeData = getBasicRunNodeData(); socket.emit('run_node', runNodeData); socket.once('current_node_running', (data) => { expect(data).to.have.property('instanceName').to.equal(basicJsonFlow[0].name); done(); }); socket.once('error', (error) => { done(new Error(`Error event received: ${JSON.stringify(error)}`)); }); }); it('run_node with missing input should trigger error event', function (done) { const processFileData = getJsonFlowWithMissingInputTextProcessFileData(); socket.emit('run_node', processFileData); socket.once('error', (error) => { done(); }); }); }); ================================================ FILE: integration_tests/tests/socketEvents/socketConnectionTest.ts ================================================ import { io, Socket } from "socket.io-client"; import { expect } from 'chai'; describe('Socket.IO connection tests', function () { let socket: Socket; beforeEach(function (done: Mocha.Done): void { socket = io('http://localhost:5000'); socket.on('connect', function (): void { done(); }); socket.on('connect_error', function (error: any): void { done(error); }); }); afterEach(function (): void { socket.disconnect(); }); it('should be connected to the server', function (done: Mocha.Done): void { expect(socket.connected).to.be.true; done(); }); it('should disconnect', function (done: Mocha.Done): void { socket.disconnect(); expect(socket.connected).to.be.false; done(); }); }); ================================================ FILE: integration_tests/tsconfig.json ================================================ { "compilerOptions": { "target": "ES6", "module": "commonjs", "outDir": "./dist", "rootDir": "./", "strict": true } } ================================================ FILE: integration_tests/utils/requestDatas.ts ================================================ type ProcessFileData = { jsonFile: string; parameters: Record; }; type RunNodeData = { jsonFile: string; parameters: Record; nodeName: string; }; export type Node = { inputs: { inputName?: string; inputNode: string; inputNodeOutputKey: number; }[]; name: string; processorType: string; [key: string]: any; }; const basicJsonFlow: Node[] = [ { inputs: [], name: "kbk1proh1#input-text", processorType: "input-text", inputText: "Hello World", x: 1, y: 1, }, ]; const jsonFlowWithMissingInputText: Node[] = [ { inputs: [], name: "kbk1proh1#input-text", processorType: "input-text", x: 1, y: 1, }, ]; export const flowWithOneNonFreeNode: Node[] = [ { inputs: [], name: "1#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", }, ]; export const sequentialFlow: Node[] = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", raiseError: false, }, { inputs: [ { inputNode: "1#llm-prompt", inputNodeOutputKey: 0, }, ], name: "2#llm-prompt", processorType: "llm-prompt", raiseError: false, }, { inputs: [ { inputNode: "2#llm-prompt", inputNodeOutputKey: 0, }, ], name: "3#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", raiseError: false, }, ]; export const flowWithoutLinks: Node[] = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", model: "gpt-4", prompt: "hi", raiseError: false, }, { inputs: [], name: "2#llm-prompt", processorType: "llm-prompt", model: "gpt-4", prompt: "hi", raiseError: false, }, { inputs: [], name: "3#stable-diffusion-stabilityai-prompt", processorType: "stable-diffusion-stabilityai-prompt", raiseError: false, }, ]; export const flowFreeNodesWithoutLink: Node[] = [ { inputs: [], name: "1#input-text", processorType: "input-text", inputText: "fake", }, { inputs: [], name: "2#input-text", processorType: "input-text", inputText: "fake", }, { inputs: [], name: "3#input-text", processorType: "input-text", inputText: "fake", }, ]; export const flowWithFourParallelNodeStep: Node[] = [ { inputs: [], name: "1#llm-prompt", processorType: "llm-prompt", raiseError: false, sleepDuration: undefined, }, { inputs: [ { inputNode: "1#llm-prompt", inputNodeOutputKey: 0, }, ], name: "2#llm-prompt", processorType: "llm-prompt", raiseError: false, sleepDuration: undefined, }, { inputs: [ { inputNode: "1#llm-prompt", inputNodeOutputKey: 0, }, ], name: "3#llm-prompt", processorType: "llm-prompt", raiseError: false, sleepDuration: undefined, }, { inputs: [ { inputNode: "1#llm-prompt", inputNodeOutputKey: 0, }, ], name: "4#llm-prompt", processorType: "llm-prompt", raiseError: false, sleepDuration: undefined, }, { inputs: [ { inputNode: "1#llm-prompt", inputNodeOutputKey: 0, }, ], name: "5#llm-prompt", processorType: "llm-prompt", raiseError: false, sleepDuration: undefined, }, ]; function getBasicProcessFileData(): ProcessFileData { return { jsonFile: JSON.stringify(basicJsonFlow), parameters: { openaiApiKey: "apiKey", }, }; } function getBasicRunNodeData(): RunNodeData { return { jsonFile: JSON.stringify(basicJsonFlow), nodeName: basicJsonFlow[0].name, parameters: { openaiApiKey: "apiKey", }, }; } function getJsonFlowWithMissingInputTextProcessFileData(): ProcessFileData { return { jsonFile: JSON.stringify(jsonFlowWithMissingInputText), parameters: { openaiApiKey: "apiKey", }, }; } function createRequestData(flow: any): ProcessFileData { return { jsonFile: JSON.stringify(flow), parameters: { openaiApiKey: "apiKey", }, }; } export { basicJsonFlow, jsonFlowWithMissingInputText, getBasicProcessFileData, getBasicRunNodeData, getJsonFlowWithMissingInputTextProcessFileData, createRequestData, }; ================================================ FILE: integration_tests/utils/testHooks.ts ================================================ import { Socket, io } from "socket.io-client"; let socket: Socket; export const setupSocket = (done: any) => { socket = io('http://localhost:5000'); socket.on('connect', function () { done(); }); socket.on('connect_error', function (error) { done(error); }); }; export const disconnectSocket = () => { socket.disconnect(); }; export const getSocket = () => socket; ================================================ FILE: packages/backend/.gitignore ================================================ # Fichiers générés par l'environnement de développement __pycache__/ *.py[cod] # Fichiers générés par l'IDE .idea/ .vscode/ # Fichiers de logs *.log # Fichiers d'env *.env # Fichiers de build build dist server.spec # Local storage local_storage/ ================================================ FILE: packages/backend/Dockerfile ================================================ FROM python:3.9 # Default values ENV HOST=0.0.0.0 ENV PORT=5000 WORKDIR /app # System dependencies RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ python3-dev \ libssl-dev \ libffi-dev \ libmagic-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Playwright ARG PLAYWRIGHT_VERSION=1.39 ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright RUN pip install playwright==$PLAYWRIGHT_VERSION && \ playwright install chromium && \ playwright install-deps chromium # Poetry & Dependencies RUN pip install --upgrade poetry \ && poetry config virtualenvs.create false COPY poetry.lock pyproject.toml /app/ RUN poetry install --no-interaction --no-root # The rest of the app COPY app /app/app/ COPY resources /app/resources COPY tests/ /app/tests/ COPY server.py README.md /app/ COPY config.yaml /app/ EXPOSE 5000 CMD ["poetry", "run", "python", "server.py"] ================================================ FILE: packages/backend/README.md ================================================ ================================================ FILE: packages/backend/app/env_config.py ================================================ import os import sys from typing import List, Optional ENV_LOCAL = "LOCAL" ENV_CLOUD = "CLOUD" CURRENT_ENV = os.environ.get("DEPLOYMENT_ENV", ENV_LOCAL) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) BACKEND_DIR = os.path.dirname(CURRENT_DIR) LOCAL_STORAGE_DIR = os.path.join( BACKEND_DIR, os.getenv("LOCAL_STORAGE_FOLDER_NAME", "local_storage") ) def get_static_folder() -> str: if getattr(sys, "frozen", False): base_path = sys._MEIPASS build_dir = os.path.join(base_path, "build") else: base_path = os.path.dirname(os.path.abspath(__file__)) build_dir = os.path.join(base_path, "..", "..", "ui", "build") return build_dir def is_cloud_env() -> bool: return CURRENT_ENV == ENV_CLOUD def is_local_environment() -> bool: return CURRENT_ENV == ENV_LOCAL def is_mock_env() -> bool: return os.getenv("USE_MOCK") == "true" def is_server_static_files_enabled() -> bool: return os.getenv("SERVE_STATIC_FILES") == "true" def get_local_storage_folder_path() -> str: return LOCAL_STORAGE_DIR def get_flask_secret_key() -> Optional[str]: return os.getenv("FLASK_SECRET_KEY") def get_replicate_api_key() -> Optional[str]: return os.getenv("REPLICATE_API_KEY") def get_background_task_max_workers() -> int: return int(os.getenv("BACKGROUND_TASK_MAX_WORKERS", "2")) def use_async_browser() -> bool: return os.getenv("USE_ASYNC_BROWSER") == "true" def get_browser_tab_max_usage() -> int: return int(os.getenv("BROWSER_TAB_MAX_USAGE", "100")) def get_browser_tab_pool_size() -> int: return int(os.getenv("BROWSER_TAB_POOL_SIZE", "3")) def is_set_app_config_on_ui_enabled() -> bool: return os.getenv("ENABLE_SET_APP_CONFIG_ON_UI", "true") == "true" def is_s3_enabled() -> bool: return os.getenv("S3_AWS_ACCESS_KEY_ID") is not None ================================================ FILE: packages/backend/app/flask/app_routes/__init__.py ================================================ ================================================ FILE: packages/backend/app/flask/app_routes/image_routes.py ================================================ from app.env_config import (get_local_storage_folder_path) from flask import Blueprint, send_from_directory image_blueprint = Blueprint('image_blueprint', __name__) @image_blueprint.route("/image/") def serve_image(filename): """ Serve image from local storage. """ return send_from_directory(get_local_storage_folder_path(), filename) ================================================ FILE: packages/backend/app/flask/app_routes/node_routes.py ================================================ import json from flask import Blueprint, request from ...utils.node_extension_utils import get_dynamic_extension_config, get_extensions # from ...utils.openapi_reader import OpenAPIReader from ...utils.replicate_utils import ( get_highlighted_models_info, get_model_openapi_schema, get_replicate_collection_models, get_replicate_collections, get_replicate_models, ) node_blueprint = Blueprint("node_blueprint", __name__) @node_blueprint.route("/node/extensions") def get_node_extensions(): extensions = get_extensions() return {"extensions": extensions} @node_blueprint.route("/node/extensions/dynamic", methods=["POST"]) def get_dynamic_extension(): request_body = request.json if request_body is None: raise Exception("Missing data") processor_type = request_body.get("processorType") data = request_body.get("data") config = get_dynamic_extension_config(processor_type, data) return config.dict() @node_blueprint.route("/node/models") def get_public_models(): cursor = request.args.get("cursor", None) public = get_replicate_models(cursor=cursor) highlighted = get_highlighted_models_info() return {"public": public, "highlighted": highlighted} @node_blueprint.route("/node/collections") def get_collections(): return get_replicate_collections() @node_blueprint.route("/node/collections/") def get_collection_models(collection): cursor = request.args.get("cursor", None) return get_replicate_collection_models(collection, cursor=cursor) @node_blueprint.route("/node/replicate/config/") def get_config(model): return get_model_openapi_schema(model) # @node_blueprint.route("/node/openapi//models") # def get_openapi_models(api_name): # api_reader = OpenAPIReader(f"./resources/openapi/{api_name}.json") # return api_reader.get_all_paths() # @node_blueprint.route("/node/openapi//config/") # def get_openapi_model_config(api_name, id): # api_reader = OpenAPIReader(f"./resources/openapi/{api_name}.json") # return api_reader.get_request_schema(id) ================================================ FILE: packages/backend/app/flask/app_routes/parameters_routes.py ================================================ import os import yaml from flask import Blueprint parameters_blueprint = Blueprint("parameters_blueprint", __name__) def load_config(): with open("config.yaml", "r") as file: return yaml.safe_load(file) @parameters_blueprint.route("/parameters", methods=["GET"]) def parameters(): config = load_config() return config ================================================ FILE: packages/backend/app/flask/app_routes/static_routes.py ================================================ import os from flask import Blueprint, send_from_directory from ...env_config import get_static_folder static_blueprint = Blueprint('static_blueprint', __name__) @static_blueprint.route("/", defaults={"path": ""}) @static_blueprint.route("/") def serve(path): """ Serve UI static files from the static folder. """ static_folder = get_static_folder() if path != "" and os.path.exists(os.path.join(static_folder, path)): return send_from_directory(static_folder, path) else: return send_from_directory(static_folder, "index.html") ================================================ FILE: packages/backend/app/flask/app_routes/upload_routes.py ================================================ import logging from flask import Blueprint from ...storage.storage_strategy import StorageStrategy from ...root_injector import get_root_injector from flask import request upload_blueprint = Blueprint("upload_blueprint", __name__) @upload_blueprint.route("/upload") def upload_file(): """ Serve image from local storage. """ logging.info("Uploading file") storage_strategy = get_root_injector().get(StorageStrategy) filename = request.args.get("filename") try: data = storage_strategy.get_upload_link(filename) except Exception as e: logging.error(e) raise Exception( "Error uploading file. " "Please check your S3 configuration. " "If you've not configured S3 please refer to docs.ai-flow.net/docs/file-upload" ) json_link = { "upload_data": data[0], "download_link": data[1], } return json_link ================================================ FILE: packages/backend/app/flask/decorators.py ================================================ from functools import wraps from flask import jsonify, request, g from flask_socketio import emit import json def with_flow_data_validations(*validation_funcs): def decorator(func): @wraps(func) def wrapper(data, *args, **kwargs): try: flow_data = json.loads(data.get("jsonFile", "{}")) for validation_func in validation_funcs: validation_func(flow_data) return func(data, *args, **kwargs) except Exception as e: emit("error", {"error": str(e)}) return wrapper return decorator ================================================ FILE: packages/backend/app/flask/flask_app.py ================================================ import logging from flask import Flask, request, redirect from flask_cors import CORS import os from ..env_config import get_flask_secret_key, get_static_folder def create_app(): app = Flask(__name__, static_folder=get_static_folder()) if get_flask_secret_key() is not None : logging.info("Flask secret key set") app.config['SECRET_KEY'] = get_flask_secret_key() else : logging.warning("Flask secret key not set") app.config['SECRET_KEY'] = "default_secret" CORS(app) if os.getenv("USE_HTTPS", "false").lower() == "true": @app.before_request def before_request(): if not request.is_secure: url = request.url.replace("http://", "https://", 1) return redirect(url, code=301) logging.info("App created") return app ================================================ FILE: packages/backend/app/flask/routes.py ================================================ import logging from app.env_config import is_server_static_files_enabled, is_local_environment from app.flask.socketio_init import flask_app from .utils.constants import HTTP_OK @flask_app.route("/healthcheck", methods=["GET"]) def healthcheck(): return "OK", HTTP_OK from .app_routes.node_routes import node_blueprint flask_app.register_blueprint(node_blueprint) from .app_routes.upload_routes import upload_blueprint flask_app.register_blueprint(upload_blueprint) from .app_routes.parameters_routes import parameters_blueprint flask_app.register_blueprint(parameters_blueprint) if is_server_static_files_enabled(): from .app_routes.static_routes import static_blueprint logging.info("Visual interface will be available at http://localhost:5000") flask_app.register_blueprint(static_blueprint) if is_local_environment(): from .app_routes.image_routes import image_blueprint logging.info("Environment set to LOCAL") flask_app.register_blueprint(image_blueprint) ================================================ FILE: packages/backend/app/flask/socketio_init.py ================================================ import eventlet eventlet.monkey_patch(all=False, socket=True) from flask_socketio import SocketIO from .flask_app import create_app flask_app = create_app() socketio = SocketIO(flask_app, cors_allowed_origins="*", async_mode="eventlet") ================================================ FILE: packages/backend/app/flask/sockets.py ================================================ import eventlet from ..env_config import is_set_app_config_on_ui_enabled eventlet.monkey_patch(all=False, socket=True) from app.flask.socketio_init import flask_app from app.flask.socketio_init import socketio import logging import json from flask import g, request, session from flask_socketio import emit from ..root_injector import ( get_root_injector, refresh_root_injector, ) from .utils.constants import PARAMETERS_FIELD_NAME, ENV_API_KEYS from ..processors.launcher.processor_launcher import ProcessorLauncher from ..processors.context.processor_context_flask_request import ( ProcessorContextFlaskRequest, ) import traceback import os def populate_request_global_object(data): """ This function is responsible for initializing individual request objects either from the environmental variables or from the data passed as arguments, ensuring that the necessary API keys are available throughout the request for different processes. Parameters: data (dict): A dictionary containing potentially necessary keys: "openai_api_key" and "stabilityai_api_key". """ use_env = os.getenv("USE_ENV_API_KEYS", "false").lower() logging.debug("use_env: %s", use_env) if use_env == "true": for key in ENV_API_KEYS: env_key = key.upper() value = os.getenv(env_key) if not value: raise Exception(f"Required {env_key} not provided in environment.") setattr(g, f"session_{key}", value) else: if not PARAMETERS_FIELD_NAME in data: raise Exception(f"No {PARAMETERS_FIELD_NAME} provided in data.") for key, value in data[PARAMETERS_FIELD_NAME].items(): if value: setattr(g, f"session_{key}", value) else: raise Exception(f"No {key} provided in data.") @socketio.on("connect") def handle_connect(): logging.info("Client connected") @socketio.on("process_file") def handle_process_file(data): """ This event handler is activated when a "process_file" event is received via Socket.IO. It allows to run every node in the file, even if they have been executed before. Parameters: data (dict): A dictionary encompassing the event's payload, which comprises the JSON configuration file ("jsonFile"). """ try: populate_request_global_object(data) flow_data = json.loads(data.get("jsonFile")) launcher = get_root_injector().get(ProcessorLauncher) launcher.set_context(ProcessorContextFlaskRequest(g, session, request.sid)) if flow_data: processors = launcher.load_processors(flow_data) output = launcher.launch_processors(processors) logging.debug("Emitting processing_result event with output: %s", output) emit("run_end", {"output": output}) else: logging.warning("Invalid input or missing configuration file") emit("error", {"error": "Invalid input or missing configuration file"}) except Exception as e: emit("error", {"error": str(e)}) traceback.print_exc() logging.error(f"An error occurred: {str(e)}") @socketio.on("run_node") def handle_run_node(data): """ This event handler is activated when a "run_node" event is received via Socket.IO. It facilitates the processing of the specified node in the data payload, launching only the designated node and preceding nodes if they haven't been executed earlier. Parameters: data (dict): A dictionary encompassing the event's payload, which comprises the JSON configuration file ("jsonFile") and the name of the node to run ("nodeName"). """ try: populate_request_global_object(data) flow_data = json.loads(data.get("jsonFile")) node_name = data.get("nodeName") launcher = get_root_injector().get(ProcessorLauncher) launcher.set_context(ProcessorContextFlaskRequest(g, session, request.sid)) if flow_data and node_name: processors = launcher.load_processors_for_node(flow_data, node_name) output = launcher.launch_processors_for_node(processors, node_name) logging.debug("Emitting processing_result event with output: %s", output) emit("run_end", {"output": output}) else: logging.warning("Invalid input or missing parameters") emit("error", {"error": "Invalid input or missing parameters"}) except Exception as e: emit( "error", {"error": str(e), "nodeName": node_name}, ) traceback.print_exc() logging.error(f"An error occurred: {node_name} - {str(e)}") @socketio.on("disconnect") def handle_disconnect(): logging.info("Client disconnected") @socketio.on("update_app_config") def handle_update_app_config(data): if not is_set_app_config_on_ui_enabled(): return logging.info("Updating app config") config_keys = [ "S3_BUCKET_NAME", "S3_AWS_ACCESS_KEY_ID", "S3_AWS_SECRET_ACCESS_KEY", "S3_AWS_REGION_NAME", "S3_ENDPOINT_URL", "REPLICATE_API_KEY", ] for key in config_keys: value = data.get(key) if value is not None and str(value).strip(): logging.info(f"Setting {key}") os.environ[key] = value refresh_root_injector() ================================================ FILE: packages/backend/app/flask/utils/constants.py ================================================ HTTP_OK = 200 HTTP_BAD_REQUEST = 400 HTTP_NOT_FOUND = 404 HTTP_UNAUTHORIZED = 401 SESSION_USER_ID_KEY = "user_id" PARAMETERS_FIELD_NAME = "parameters" ENV_API_KEYS = [ "openai_api_key", "stabilityai_api_key", "replicate_api_key", "anthropic_api_key", "openrouter_api_key", ] ================================================ FILE: packages/backend/app/llms/utils/max_token_for_model.py ================================================ import tiktoken DEFAULT_MAX_TOKEN = 4097 def max_token_for_model(model_name: str) -> int: if "gpt-4o" in model_name: return 128000 token_data = { # GPT-4.1 models "gpt-4.1": 1047576, "gpt-4.1-mini": 1047576, "gpt-4.1-nano": 1047576, # GPT-4 models "gpt-4o": 128000, "gpt-4o-2024-11-20": 128000, "gpt-4o-mini": 128000, "gpt-4-turbo": 128000, "gpt-4-turbo-preview": 128000, "gpt-4-1106-preview": 128000, "gpt-4-vision-preview": 128000, "gpt-4": 8192, "gpt-4-0613": 8192, "gpt-4-32k": 32768, "gpt-4-32k-0613": 32768, "gpt-4-0314": 8192, "gpt-4-32k-0314": 32768, # GPT-3.5 models "gpt-3.5-turbo": 16385, "gpt-3.5-turbo-1106": 16385, "gpt-3.5-turbo-16k": 16385, "gpt-3.5-turbo-instruct": 4097, "gpt-3.5-turbo-0613": 4097, "gpt-3.5-turbo-16k-0613": 16385, "gpt-3.5-turbo-0301": 4097, # Other GPT-3.5 models "text-davinci-003": 4097, "text-davinci-002": 4097, "code-davinci-002": 8001, } return token_data.get(model_name, DEFAULT_MAX_TOKEN) def nb_token_for_input(input: str, model_name: str) -> int: try: return len(tiktoken.encoding_for_model(model_name).encode(input)) except Exception as e: default_model_for_token = "gpt-4o" return len(tiktoken.encoding_for_model(default_model_for_token).encode(input)) ================================================ FILE: packages/backend/app/log_config.py ================================================ import logging import colorlog def setup_logger(name: str): formatter = colorlog.ColoredFormatter( "%(log_color)s%(levelname)-8s%(reset)s %(message)s", datefmt=None, reset=True, log_colors={ "DEBUG": "cyan", "INFO": "green", "WARNING": "yellow", "ERROR": "red", "CRITICAL": "red", }, ) logger = logging.getLogger(name) handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler) return logger root_logger = setup_logger("root") root_logger.setLevel(logging.INFO) ================================================ FILE: packages/backend/app/processors/components/__init__.py ================================================ ================================================ FILE: packages/backend/app/processors/components/core/__init__.py ================================================ ================================================ FILE: packages/backend/app/processors/components/core/ai_data_splitter_processor.py ================================================ import logging from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor from .processor_type_name_utils import ProcessorType from openai import OpenAI def interpret_escape_sequences(separator): escape_dict = { r"\n": "\n", r"\r": "\r", r"\t": "\t", } return escape_dict.get(separator, separator) class AIDataSplitterProcessor(ContextAwareProcessor): processor_type = ProcessorType.AI_DATA_SPLITTER DEFAULT_SEPARATOR = ";" AI_MODE = "ai" MANUAL_MODE = "manual" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.nb_output = 0 self.model = "gpt-4o" self.api_key = context.get_value("openai_api_key") def get_llm_response(self, messages): client = OpenAI(api_key=self.api_key) kwargs = {"model": self.model, "input": messages} response = client.responses.create(**kwargs) return response.output_text def process(self): if self.get_input_processor() is None: return "" input_data = self.get_input_processor().get_output( self.get_input_node_output_key() ) mode = self.get_input_by_name("mode", self.AI_MODE) if mode == self.AI_MODE: self.init_context(input_data) answer = self.get_llm_response(self.messages) data_to_split = answer.encode("utf-8").decode("utf8") self.set_output( data_to_split.split(AIDataSplitterProcessor.DEFAULT_SEPARATOR) ) self.nb_output = len(self._output) if mode == self.MANUAL_MODE: separator = self.get_input_by_name( "separator", AIDataSplitterProcessor.DEFAULT_SEPARATOR ) separator = interpret_escape_sequences(separator) self.set_output(input_data.split(separator)) self.nb_output = len(self._output) return self._output def init_context(self, input_data: str) -> None: """ Initialize the context for the OpenAI Chat model with a set of standard messages. Additional user input data can be provided, which will be added to the messages. :param input_data: Additional information or text provided by the user that needs processing. """ # Define the system message with clear instructions and examples system_msg = ( "You are an assistant whose task is to separate ideas or concepts from the input text using semicolons (;). " "Do not include any meta-comments or self-references in your responses. " "Here are some examples of how to perform the task: " "\n\n" "Example 1:\n" "Input: 'The main idea is that dogs are very popular pets, and many people enjoy walking them in parks. Another important concept is that dogs need a lot of exercise to stay healthy.'\n" "Output: 'Dogs are very popular pets; many people enjoy walking them in parks; dogs need a lot of exercise to stay healthy.'\n\n" "Example 2:\n" "Input: '1) A picture of a woman 2) A video with a bird 3) Air conditioner'\n" "Output: 'A picture of a woman; A video with a bird; Air conditioner.'\n\n" "Example 3:\n" "Input: 'Here are two ideas: - Dogs are better than cats - Birds are beautiful'\n" "Output: 'Dogs are better than cats; Birds are beautiful.'\n\n" "Example 4:\n" "Input: 'Crée une interprétation artistique numérique de la ville de New York la nuit sous la pluie, mettant l'accent sur les reflets lumineux sur les surfaces mouillées. Imagine et dessine un nouveau type de fleur qui n'existe pas encore dans la nature. Assure-toi qu'elle a une allure exotique et utilise des couleurs vives et uniques que l'on ne trouve pas couramment chez les fleurs. Conçois une image représentant une scène du futur, avec des villes futuristes, des technologies avancées et des formes de vie artificielles coexistant avec des formes de vie naturelles.'\n" "Output: 'Crée une interprétation artistique numérique de la ville de New York la nuit sous la pluie, mettant l'accent sur les reflets lumineux sur les surfaces mouillées; Imagine et dessine un nouveau type de fleur qui n'existe pas encore dans la nature. Assure-toi qu'elle a une allure exotique et utilise des couleurs vives et uniques que l'on ne trouve pas couramment chez les fleurs; Conçois une image représentant une scène du futur, avec des villes futuristes, des technologies avancées et des formes de vie artificielles coexistant avec des formes de vie naturelles.'\n\n" "After reading the input, output each distinct idea or concept separated by semicolons." ) user_nb_output = self.get_input_by_name("nb_output", 0) if user_nb_output > 1: system_msg += f"\nThe estimated number of outputs for the next message is {user_nb_output}." self.messages = [ {"role": "system", "content": system_msg}, {"role": "user", "content": input_data}, ] def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/dall_e_prompt_processor.py ================================================ from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor from openai import OpenAI from .processor_type_name_utils import ProcessorType class DallEPromptProcessor(ContextAwareProcessor): processor_type = ProcessorType.DALLE_PROMPT DEFAULT_MODEL = "dall-e-3" DEFAULT_SIZE = "1024x1024" DEFAULT_QUALITY = "standard" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.prompt = config.get("prompt") self.size = config.get("size", DallEPromptProcessor.DEFAULT_SIZE) self.quality = config.get("quality", DallEPromptProcessor.DEFAULT_QUALITY) def process(self): if self.get_input_processor() is not None: self.prompt = ( self.get_input_processor().get_output(self.get_input_node_output_key()) if self.prompt is None or len(self.prompt) == 0 else self.prompt ) api_key = self._processor_context.get_value("openai_api_key") client = OpenAI( api_key=api_key, ) response = client.images.generate( model=DallEPromptProcessor.DEFAULT_MODEL, prompt=self.prompt, n=1, size=self.size, quality=self.quality, ) return response.data[0].url def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/display_processor.py ================================================ from .processor_type_name_utils import ProcessorType from ..processor import BasicProcessor class DisplayProcessor(BasicProcessor): processor_type = "display" def __init__(self, config): super().__init__(config) def process(self): input_data = None if self.get_input_processor() is None: return "" input_data = self.get_input_processor().get_output( self.get_input_node_output_key() ) return input_data ================================================ FILE: packages/backend/app/processors/components/core/file_processor.py ================================================ from .processor_type_name_utils import ProcessorType from ..processor import BasicProcessor class FileProcessor(BasicProcessor): processor_type = ProcessorType.FILE def __init__(self, config): super().__init__(config) self.url = config["fileUrl"] def process(self): return self.url ================================================ FILE: packages/backend/app/processors/components/core/gpt_vision_processor.py ================================================ import re from typing import Any, List from ...launcher.event_type import EventType from ...launcher.processor_event import ProcessorEvent from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor from .processor_type_name_utils import ProcessorType from openai import OpenAI from urllib.parse import urlparse class GPTVisionProcessor(ContextAwareProcessor): processor_type = ProcessorType.GPT_VISION DEFAULT_MODEL = "gpt-4o" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def _gather_image_url_values(self) -> List[Any]: """ Pull the value of `element` plus every `element_` child field in the order they appear in self.fields_names. """ # Match element_0, element_1, … whatever the UI generates child_pattern = re.compile(r"^image_url_\d+$") # Preserve original order: parent first, then the children ordered_field_names = [ fname for fname in self.fields_names if fname == "image_url" or child_pattern.match(fname) ] values = [self.get_input_by_name(fname, None) for fname in ordered_field_names] return [v for v in values if v is not None] def process(self): self.vision_inputs = { "prompt": self.get_input_by_name("prompt"), } images_urls = self._gather_image_url_values() if ( self.vision_inputs["prompt"] is None or len(self.vision_inputs["prompt"]) == 0 ): raise ValueError("No prompt provided.") if len(images_urls) == 0: raise ValueError("No image provided.") for url in images_urls: if not self.is_valid_url(url): raise ValueError(f"Invalid URL provided. \n {url}") api_key = self._processor_context.get_value("openai_api_key") client = OpenAI( api_key=api_key, ) content = [] for image in images_urls: content.append( { "type": "image_url", "image_url": {"url": image}, } ) content.append( { "type": "text", "text": self.vision_inputs["prompt"], } ) response = client.chat.completions.create( model=GPTVisionProcessor.DEFAULT_MODEL, messages=[ { "role": "user", "content": content, } ], max_tokens=4096, stream=True, ) final_response = "" for chunk in response: if not chunk.choices[0].delta.content: continue final_response += chunk.choices[0].delta.content event = ProcessorEvent(self, final_response) self.notify(EventType.STREAMING, event) return final_response def is_valid_url(self, url): try: result = urlparse(url) return all([result.scheme, result.netloc]) except Exception: return False def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/input_image_processor.py ================================================ from .processor_type_name_utils import ProcessorType from ..processor import BasicProcessor class InputImageProcessor(BasicProcessor): processor_type = ProcessorType.INPUT_IMAGE def __init__(self, config): super().__init__(config) self.inputText = config["inputText"] def process(self): return self.inputText ================================================ FILE: packages/backend/app/processors/components/core/input_processor.py ================================================ from .processor_type_name_utils import ProcessorType from ..processor import BasicProcessor class InputProcessor(BasicProcessor): processor_type = ProcessorType.INPUT_TEXT def __init__(self, config): super().__init__(config) self.inputText = config["inputText"] def process(self): return self.inputText ================================================ FILE: packages/backend/app/processors/components/core/llm_prompt_processor.py ================================================ import logging from app.processors.exceptions import LightException from ...launcher.processor_event import ProcessorEvent from ...launcher.event_type import EventType from ....llms.utils.max_token_for_model import max_token_for_model, nb_token_for_input from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor from openai import OpenAI from .processor_type_name_utils import ProcessorType class LLMPromptProcessor(ContextAwareProcessor): processor_type = ProcessorType.LLM_PROMPT DEFAULT_MODEL = "gpt-4o" streaming = True models_with_web_search = [ "gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini", ] def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.model = config.get("model", LLMPromptProcessor.DEFAULT_MODEL) self.prompt = config.get("prompt", None) def handle_stream_answer(self, awnser): event = ProcessorEvent(self, awnser) self.notify(EventType.STREAMING, event) def nb_tokens_from_messages(self, messages, model): """ Calculates the total number of tokens in a list of messages using nb_token_for_input. """ total_tokens = 0 token_overhead = 3 for message in messages: content_tokens = nb_token_for_input(message["content"], model) total_tokens += content_tokens + token_overhead total_tokens += token_overhead return total_tokens def check_for_html_tags(self, text): """ Checks if the given text contains HTML tags or attributes. """ if " 1: context = self.get_input_by_name("context", None) self.prompt = self.get_input_by_name("prompt", None) else: if self.get_input_processor() is not None: context = self.get_input_processor().get_output( self.get_input_node_output_key() ) if self.prompt is None: raise Exception("No prompt provided") self.init_context(context) total_tokens = self.nb_tokens_from_messages(self.messages, self.model) model_max_tokens = max_token_for_model(self.model) if total_tokens > model_max_tokens: logging.warning("Messages size: " + str(total_tokens)) logging.warning("Model capacity: " + str(model_max_tokens)) message = ( "The text size exceeds the model's capacity. " "Consider using a model with greater context handling capabilities or utilize the 'Find Similar Text' node to create a cohesive, condensed version of the context." ) if ( context and self.check_for_html_tags(context) ) or self.check_for_html_tags(self.prompt): message += ( "\n\n" "Note: HTML tags or attributes are detected within the data provided. If they are unnecessary for this task, removing them could significantly reduce the context size." ) raise Exception(message) client = OpenAI(api_key=api_key) kwargs = {"model": self.model, "input": self.messages, "stream": self.streaming} if search_enabled: kwargs["tools"] = [ { "type": "web_search_preview", "search_context_size": search_context_size, } ] stream = client.responses.create(**kwargs) final_response = "" for event in stream: type = event.type if type == "response.output_text.delta": final_response += event.delta self.handle_stream_answer(final_response) if type == "response.completed": response_data = event.response final_response = response_data.output_text if type == "response.failed": response_data = event.response if not hasattr(response_data, "error"): logging.warning(f"Error from OpenAI with no data: {response_data}") continue raise LightException( f"Error from OpenAI : {response_data.error.message}" ) if type == "error": raise LightException(f"Error from OpenAI : {event.message}") return final_response def init_context(self, context: str) -> None: """ Initialise the context for the LLM model with a standard set of messages. Additional user input data can be provided, which will be added to the messages. :param context: additional information to be used by the assistant. """ if context is None: system_msg = "You are a helpful assistant. " user_msg_content = self.prompt else: system_msg = ( "You are a helpful assistant. " "You will respond to requests indicated by the '#Request' tag, " "using the context provided under the '#Context' tag." "Your response should feel natural and seamless, as if you've internalized the context " "and are answering the request without needing to directly point back to the information provided" ) user_msg_content = f"#Context: {context} \n\n#Request: {self.prompt}" self.messages = [ {"role": "system", "content": system_msg}, {"role": "user", "content": user_msg_content}, ] def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/merge_processor.py ================================================ from ..processor import ContextAwareProcessor from .processor_type_name_utils import ProcessorType, MergeModeEnum class MergeProcessor(ContextAwareProcessor): processor_type = ProcessorType.MERGER_PROMPT def __init__(self, config, context): super().__init__(config, context) self.merge_mode = MergeModeEnum(int(config["mergeMode"])) def update_prompt(self, inputs): for idx, value in enumerate(inputs, start=1): placeholder = f"${{input-{idx}}}" self.prompt = self.prompt.replace(placeholder, str(value)) def process(self): self.prompt = self.get_input_by_name("prompt", "") input_names = self.get_input_names_from_config() inputs = [self.get_input_by_name(name, "") for name in input_names] self.update_prompt(inputs) return self.prompt def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/processor_type_name_utils.py ================================================ from enum import Enum class MergeModeEnum(Enum): MERGE = 1 MERGE_AND_PROMPT = 2 class ProcessorType(Enum): INPUT_TEXT = "input-text" INPUT_IMAGE = "input-image" URL_INPUT = "url_input" LLM_PROMPT = "llm-prompt" GPT_VISION = "gpt-vision" YOUTUBE_TRANSCRIPT_INPUT = "youtube_transcript_input" DALLE_PROMPT = "dalle-prompt" STABLE_DIFFUSION_STABILITYAI_PROMPT = "stable-diffusion-stabilityai-prompt" STABLE_VIDEO_DIFFUSION_REPLICATE = "stable-video-diffusion-replicate" REPLICATE = "replicate" MERGER_PROMPT = "merger-prompt" AI_DATA_SPLITTER = "ai-data-splitter" TRANSITION = "transition" DISPLAY = "display" FILE = "file" STABLE_DIFFUSION_THREE = "stabilityai-stable-diffusion-3-processor" TEXT_TO_SPEECH = "openai-text-to-speech-processor" DOCUMENT_TO_TEXT = "document-to-text-processor" STABILITYAI = "stabilityai-generic-processor" CLAUDE = "claude-anthropic-processor" REPLACE_TEXT = "replace-text" ================================================ FILE: packages/backend/app/processors/components/core/replicate_processor.py ================================================ from datetime import datetime import logging from queue import Queue import time from urllib.parse import urlparse from app.env_config import is_s3_enabled from ...launcher.event_type import EventType from ...launcher.processor_event import ProcessorEvent from ....utils.processor_utils import stream_download_file_as_binary from ...exceptions import LightException from ....utils.replicate_utils import ( get_input_schema_from_open_API_schema, get_model_openapi_schema, get_output_schema_from_open_API_schema, ) from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor import replicate from .processor_type_name_utils import ProcessorType from ....tasks.task_exception import TaskAlreadyRegisteredError from ....tasks.thread_pool_task_manager import add_task, register_task_processor from ....tasks.task_utils import wait_for_result class ReplicateProcessor(ContextAwareProcessor): processor_type = ProcessorType.REPLICATE def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.is_processing = False self.config = config self.model = config.get("model") if self.model is None: self.model = config.get("config").get("nodeName") if ":" not in self.model: logging.warning(f"Model {self.model} has no version") raise Exception(f"Cannot find version for this model : {self.model}.") self.model_name_withouth_version = self.model.split(":")[0] def get_prediction_result( self, prediction, processor, timeout=3600.0, initial_sleep=0.1, max_sleep=5.0 ): results_queue = Queue() add_task("replicate_prediction_wait", (prediction, processor), results_queue) try: prediction = wait_for_result( results_queue, timeout, initial_sleep, max_sleep ) except TimeoutError as e: raise TimeoutError("Prediction result timed out") return prediction @staticmethod def wait_for_prediction_task(task_data): prediction, processor = task_data while prediction.status not in ["succeeded", "failed", "canceled"]: time.sleep(prediction._client.poll_interval) if prediction.status == "processing": processor.is_processing = True prediction.reload() return prediction def register_background_task(self): try: register_task_processor( "replicate_prediction_wait", self.wait_for_prediction_task, max_concurrent_tasks=100, ) except TaskAlreadyRegisteredError as e: pass def process(self): api_key = self._processor_context.get_value("replicate_api_key") self.schema = get_model_openapi_schema(self.model_name_withouth_version) input_processors = self.get_input_processors() input_output_keys = self.get_input_node_output_keys() input_names = self.get_input_names() if input_processors: for processor, name, key in zip( input_processors, input_names, input_output_keys ): output = processor.get_output(key) if output is None: continue input_type = self._get_nested_input_schema_property(name, "type") if input_type == "integer": output = int(output) if input_type == "number": output = float(output) self.config[name] = output api = replicate.Client(api_token=api_key) output_schema = get_output_schema_from_open_API_schema(self.schema["schema"]) logging.debug(f"Output schema : {output_schema}") output_type = output_schema.get("type") output_array_display = output_schema.get("x-cog-array-display") output_format = output_schema.get("format") if not ":" in self.model: logging.warning(f"Model {self.model} has no version") raise Exception("Cannot find version for this model") rest, version_id = self.model.split(":") self.config["disable_safety_checker"] = True try: self.prediction = api.predictions.create( version=version_id, input=self.config ) except Exception as e: logging.warning(f"Error while creating prediction : {e}") raise LightException( "Please review your input to ensure it aligns with the expected format. \n\n" "For reference, you can review the examples here: \n" f"https://replicate.com/{self.model_name_withouth_version}/examples\n\n" f"Error message from Replicate: \n\n {e}" ) self.register_background_task() self.prediction = self.get_prediction_result(self.prediction, self) if self.prediction.status != "succeeded": replicate_error_message = self.prediction.error message_str = f"Your Replicate prediction ended with status : {self.prediction.status} \n\n" if replicate_error_message and self.prediction.status != "canceled": message_str += ( f"There may be an issue with the parameters provided for the model '{self.model_name_withouth_version}'. \n\n" "Please review your input to ensure it aligns with the expected format. \n\n" "For reference, you can review the examples here: \n" f"https://replicate.com/{self.model_name_withouth_version}/examples\n\n" f"Error message from Replicate: {replicate_error_message}" ) exception = Exception(message_str) exception.rollback_not_needed = True raise exception output = self.prediction.output self.metrics = self.prediction.metrics isUriOutput = output_format == "uri" if output_type == "array" and output_array_display == "concatenate": output = "".join(output) elif output_type == "array": items_type = output_schema.get("items").get("type") items_format = output_schema.get("items").get("format") isUriOutput = items_format == "uri" output = output elif output_type == "string": if isinstance(output, list): output = "".join(output) else: output = [output] event = ProcessorEvent(self, output) self.notify(EventType.STREAMING, event) if isUriOutput: if isinstance(output, list): new_output = [] for uri in output: new_uri = self.upload_replicate_uri_to_storage(uri) new_output.append(new_uri) output = new_output else: output = self.upload_replicate_uri_to_storage(output) return output def upload_replicate_uri_to_storage(self, uri): if not is_s3_enabled(): return uri storage = self.get_storage() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") extension = None try: parsed = urlparse(uri) path = parsed.path if "." in path: extension = path.split(".")[-1] else: logging.warning("No extension found in URI: %s", uri) except Exception as e: logging.warning("Error extracting extension from URI (%s): %s", uri, str(e)) if not extension: logging.warning("Aborting Upload - No extension found in URI: %s", uri) return uri filename = f"{self.name}-{timestamp_str}.{extension}" file = stream_download_file_as_binary(uri) url = storage.save(filename, file) return url def _get_nested_input_schema_property(self, property_name, nested_key): return ( get_input_schema_from_open_API_schema(self.schema.get("schema", {})) .get("properties", {}) .get(property_name, {}) .get(nested_key) ) def cancel(self): api_key = self._processor_context.get_value("replicate_api_key") api = replicate.Client(api_token=api_key) api.predictions.cancel(id=self.prediction.id) ================================================ FILE: packages/backend/app/processors/components/core/stable_diffusion_stabilityai_prompt_processor.py ================================================ import base64 from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor from datetime import datetime import requests import os from .processor_type_name_utils import ProcessorType class StableDiffusionStabilityAIPromptProcessor(ContextAwareProcessor): processor_type = ProcessorType.STABLE_DIFFUSION_STABILITYAI_PROMPT def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.prompt = config.get("prompt") size = config.get("size", "1024x1024") self.height = int(size.split("x")[0]) self.width = int(size.split("x")[1]) self.style_preset = config.get("style_preset", "") self.samples = 1 self.engine_id = "stable-diffusion-xl-1024-v1-0" self.api_host = os.getenv( "STABLE_DIFFUSION_STABILITYAI_API_HOST", "https://api.stability.ai" ) def prepare_and_process_response(self, response): if response.status_code != 200: raise Exception("Non-200 response: " + str(response.text)) data = response.json() first_image = data["artifacts"][0]["base64"] image_data = base64.b64decode(first_image) storage = self.get_storage() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") filename = f"{self.name}-{timestamp_str}.png" url = storage.save(filename, image_data) return url def setup_data_to_send(self): if self.get_input_processor() is not None: self.prompt = ( self.get_input_processor().get_output(self.get_input_node_output_key()) if self.prompt is None or len(self.prompt) == 0 else self.prompt ) data_to_send = { "text_prompts": [{"text": f"{self.prompt}"}], "cfg_scale": 7, "height": self.height, "width": self.width, "samples": self.samples, "steps": 30, } return data_to_send def process(self): data_to_send = self.setup_data_to_send() api_key = self._processor_context.get_value("stabilityai_api_key") response = requests.post( f"{self.api_host}/v1/generation/{self.engine_id}/text-to-image", headers={ "Content-Type": "application/json", "Accept": "application/json", "Authorization": f"Bearer {api_key}", }, json=data_to_send, ) return self.prepare_and_process_response(response) def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/stable_video_diffusion_replicate.py ================================================ import os from urllib.parse import urlparse from ...context.processor_context import ProcessorContext from ..processor import ContextAwareProcessor import replicate from .processor_type_name_utils import ProcessorType class StableVideoDiffusionReplicaterocessor(ContextAwareProcessor): processor_type = ProcessorType.STABLE_VIDEO_DIFFUSION_REPLICATE stable_video_diffusion_model = "stability-ai/stable-video-diffusion:3f0457e4619daac51203dedb472816fd4af51f3149fa7a9e0b5ffcf1b8172438" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.length = config.get("length", "14_frames_with_svd") self.frames_per_second = config.get("frames_per_second", "6") def process(self): input_image_url = None if self.get_input_processor() is not None: input_image_url = self.get_input_processor().get_output( self.get_input_node_output_key() ) if input_image_url is None: return "No image provided." if not self.is_valid_url(input_image_url): return "Invalid URL provided." api_key = self._processor_context.get_value("replicate_api_key") api = replicate.Client(api_token=api_key) output = api.run( StableVideoDiffusionReplicaterocessor.stable_video_diffusion_model, input={ "cond_aug": 0.02, "decoding_t": 7, "input_image": input_image_url, "video_length": self.length, "sizing_strategy": "maintain_aspect_ratio", "motion_bucket_id": 127, "frames_per_second": int(self.frames_per_second), }, ) return output def is_valid_url(self, url): try: result = urlparse(url) return all([result.scheme, result.netloc]) except Exception: return False def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/core/transition_processor.py ================================================ from .processor_type_name_utils import ProcessorType from ..processor import BasicProcessor class TransitionProcessor(BasicProcessor): processor_type = ProcessorType.TRANSITION def __init__(self, config): super().__init__(config) def process(self): input_data = None if self.get_input_processor() is None: return "" input_data = self.get_input_processor().get_output( self.get_input_node_output_key() ) return input_data ================================================ FILE: packages/backend/app/processors/components/core/url_input_processor.py ================================================ import random from bs4 import BeautifulSoup import requests from ....utils.processor_utils import is_valid_url from ..processor import BasicProcessor from .processor_type_name_utils import ProcessorType import logging from markdownify import markdownify class URLInputProcessor(BasicProcessor): WAIT_TIMEOUT = 60 GET_TIMEOUT = 20 processor_type = ProcessorType.URL_INPUT USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0", ] def __init__(self, config): super().__init__(config) def get_random_user_agent(): return random.choice(URLInputProcessor.USER_AGENTS) def fetch_content_simple(self): """ Fetches the website content using a simple GET request. """ try: headers = {"User-Agent": URLInputProcessor.get_random_user_agent()} response = requests.get(self.url, headers=headers, timeout=self.GET_TIMEOUT) response.raise_for_status() return response.text except requests.RequestException as e: logging.warning(f"Failed to fetch content using simple GET: {e}") return None def process(self): self.url = self.get_input_by_name("url") self.loading_mode = self.get_input_by_name("loading_mode", "browser") self.effective_load_mode = self.loading_mode # Validate URL input if not self.url or not isinstance(self.url, str) or self.url.strip() == "": raise Exception("No URL provided.", "noURLProvided") self.url = self.url.strip() self.original_url = self.url if not (self.url.startswith("https://") or self.url.startswith("http://")): logging.warning( "URL does not start with 'https://' or 'http://' - compensating by prepending 'https://'." ) self.url = "https://" + self.url if not is_valid_url(self.url): logging.warning(f"Invalid URL: {self.url}") raise Exception( f"The provided URL '{self.original_url}' is not valid.\n\n" "Please ensure the URL follows the correct format, e.g., 'https://www.example.com' or 'https://example.com'." ) # Get additional parameters self.selectors = self.get_input_by_name("selectors", []) self.selectors_to_remove = self.get_input_by_name("selectors_to_remove", []) self.with_html_tags = self.get_input_by_name("with_html_tags", False) self.with_html_attributes = self.get_input_by_name( "with_html_attributes", False ) response = None task_data = { "url": self.url, "selectors": self.selectors, "selectors_to_remove": self.selectors_to_remove, "with_html_tags": self.with_html_tags, "with_html_attributes": self.with_html_attributes, } content = self.fetch_content_simple() response = self.process_content_with_beautiful_soup(content, task_data) return response def process_content_with_beautiful_soup(self, content, task_data): """ Process the HTML content using BeautifulSoup while considering the following parameters: - selectors: a list of CSS selectors; if provided, only matching elements are kept. - selectors_to_remove: a list of CSS selectors for elements that should be removed. - with_html_tags: if True, the returned result will include HTML tags; otherwise, plain text. - with_html_attributes: if True (and with_html_tags is True), HTML attributes will be kept; otherwise, they will be stripped. """ if not content: return "" soup = BeautifulSoup(content, "html.parser") selectors = task_data.get("selectors", []) if isinstance(selectors, str): selectors = [selectors] selectors_to_remove = task_data.get("selectors_to_remove", []) if isinstance(selectors_to_remove, str): selectors_to_remove = [selectors_to_remove] for selector in selectors_to_remove: for element in soup.select(selector): element.decompose() if selectors: selected_elements = soup.select(", ".join(selectors)) if not selected_elements: selected_elements = [soup] else: selected_elements = [soup] with_html_tags = task_data.get("with_html_tags", False) with_html_attributes = task_data.get("with_html_attributes", False) if with_html_tags: if not with_html_attributes: for element in selected_elements: if hasattr(element, "attrs"): element.attrs = {} for tag in element.find_all(True): tag.attrs = {} html_output = "".join(str(element) for element in selected_elements) return html_output else: html_output = "".join(str(element) for element in selected_elements) text_output = markdownify(html_output) return text_output ================================================ FILE: packages/backend/app/processors/components/core/youtube_transcript_input_processor.py ================================================ import logging from ...utils.retry_mixin import RetryMixin from ...exceptions import LightException from ..processor import BasicProcessor from youtube_transcript_api import ( YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound, VideoUnavailable, ) from .processor_type_name_utils import ProcessorType class YoutubeTranscriptInputProcessor(BasicProcessor, RetryMixin): processor_type = ProcessorType.YOUTUBE_TRANSCRIPT_INPUT def __init__(self, config): super().__init__(config) self.max_retries = 2 self.retry_delay = 0 def get_video_id(self): if "watch?v=" in self.url: return self.url.split("watch?v=")[-1].split("&")[0] elif "youtu.be/" in self.url: return self.url.split("youtu.be/")[-1].split("?")[0] else: raise LightException(f"Invalid YouTube URL {self.url}") def process_with_youtube_transcript_api(self): video_id = self.get_video_id() try: transcript_data = self.get_transcript(video_id) except (TranscriptsDisabled, NoTranscriptFound) as e: logging.warning( f"Transcript not available or disabled for video {self.url}" ) logging.debug(e) raise Exception(f"No transcription found for {self.url}") except VideoUnavailable as e: logging.warning(f"Video is unavailable") logging.debug(e) raise Exception(f"Video is unavailable for {self.url}") except Exception as e: logging.warning(f"Failed to retrieve transcript") logging.debug(e) raise Exception(self.create_no_transcript_error_message(e)) content = " ".join([entry["text"] for entry in transcript_data]) if not content: raise Exception(f"No transcription found for {self.url}") return content def get_transcript(self, video_id): """Attempts to get the transcript in the requested language or translate if not available.""" try: # Try to get the transcript in the requested language return YouTubeTranscriptApi.get_transcript( video_id, languages=[self.language] ) except NoTranscriptFound: # If transcript in the requested language is not found, try to find a translatable one return self.get_translatable_transcript(video_id) except Exception as e: logging.debug(f"Failed to retrieve transcript with first proxy") logging.debug(e) # Retry with a new proxy return YouTubeTranscriptApi.get_transcript( video_id, languages=[self.language] ) def get_translatable_transcript(self, video_id): """Finds a translatable transcript and translates it to the requested language.""" try: # List all transcripts for the video transcripts = YouTubeTranscriptApi.list_transcripts(video_id) # Find an auto-generated, translatable transcript for transcript in transcripts: if transcript.is_translatable: return transcript.translate(self.language).fetch() # Raise an exception if no translatable transcript is found raise NoTranscriptFound( f"No translatable transcript available for video {video_id}" ) except Exception as e: logging.warning(f"Failed to find a translatable transcript") logging.debug(e) raise def create_no_transcript_error_message(self, e): requested_languages = getattr(e, "_requested_language_codes", None) transcript_data = getattr(e, "_transcript_data", None) error_message = f"Failed to retrieve transcript for {self.url} \n\nRequested Language: {requested_languages} \n\n{transcript_data}" return error_message def retrieve_transcript(self, url, language): self.url = url self.language = language if not self.url: raise Exception("No URL provided") content = self.run_with_retry(self.process_with_youtube_transcript_api) if not content: raise Exception(f"No transcription found for {self.url}") logging.info(f"Transcription for {self.url} retrieved successfully") return content def process(self): url = self.get_input_by_name("url") language = self.get_input_by_name("language") logging.info(language) return self.retrieve_transcript(url, language) ================================================ FILE: packages/backend/app/processors/components/extension/__init__.py ================================================ ================================================ FILE: packages/backend/app/processors/components/extension/claude_anthropic_processor.py ================================================ import logging from datetime import datetime import anthropic from ...context.processor_context import ProcessorContext from ..model import Field, NodeConfig, Option, Condition, ConditionGroup from .extension_processor import ContextAwareExtensionProcessor from ...launcher.processor_event import ProcessorEvent from ...launcher.event_type import EventType class ClaudeAnthropicProcessor(ContextAwareExtensionProcessor): processor_type = "claude-anthropic-processor" model_config_map = { "claude-3-7-sonnet-latest": { "max_tokens": 8192, "max_tokens_thinking": 64000, }, "claude-3-5-haiku-latest": { "max_tokens": 8192, "max_tokens_thinking": 8192, }, "claude-3-5-sonnet-latest": { "max_tokens": 8192, "max_tokens_thinking": 8192, }, "claude-3-opus-latest": { "max_tokens": 4096, "max_tokens_thinking": 4096, }, "claude-3-haiku-20240307": { "max_tokens": 4096, "max_tokens_thinking": 4096, }, "claude-3-5-sonnet-20240620": { "max_tokens": 8192, "max_tokens_thinking": 8192, }, "claude-opus-4-0": { "max_tokens": 32000, "max_tokens_thinking": 32000, }, "claude-sonnet-4-0": { "max_tokens": 64000, "max_tokens_thinking": 64000, }, } def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.reasoning_content = "" def get_node_config(self): # Conditions claude_thinking_condition = Condition( field="model", operator="in", value=["claude-3-7-sonnet-latest", "claude-opus-4-0", "claude-sonnet-4-0"], ) thinking_enabled_condition = Condition( field="thinking", operator="equals", value=True ) budget_token_condition = ConditionGroup( conditions=[claude_thinking_condition, thinking_enabled_condition], logic="AND", ) # Fields prompt = Field( name="prompt", label="prompt", type="textarea", required=True, placeholder="InputTextPlaceholder", hasHandle=True, ) prompt_context = Field( name="context", label="context", type="textfield", placeholder="InputTextPlaceholder", hasHandle=True, description="Additional context that will be used to answer your prompt.", ) temperature = Field( name="temperature", label="temperature", type="slider", min=0, max=1, defaultValue=1, placeholder="InputTextPlaceholder", description="Use temperature closer to 0.0 for analytical tasks, and closer to 1.0 for creative tasks.", ) budget_token = Field( name="budget_tokens", label="budget_tokens", type="slider", defaultValue=1024, max=63999, min=1024, condition=budget_token_condition, description=( "Determines how many tokens Claude can use for its internal reasoning process. " "Larger budgets can enable more thorough analysis for complex problems, improving response quality." ), ) model_options = [ Option( default=False, value="claude-3-7-sonnet-latest", label="Claude 3.7 Sonnet", ), Option( default=False, value="claude-3-5-haiku-latest", label="Claude 3.5 Haiku", ), Option( default=False, value="claude-3-5-sonnet-latest", label="Claude 3.5 Sonnet", ), Option( default=False, value="claude-3-opus-latest", label="Claude 3 Opus", ), Option( default=False, value="claude-3-haiku-20240307", label="Claude 3 Haiku", ), Option( default=False, value="claude-opus-4-0", label="Claude 4 Opus", ), Option( default=True, value="claude-sonnet-4-0", label="Claude 4 Sonnet", ), ] model = Field( name="model", label="model", type="select", options=model_options, required=True, ) thinking = Field( name="thinking", label="thinking", type="boolean", condition=claude_thinking_condition, ) fields = [ prompt, prompt_context, model, thinking, budget_token, temperature, ] config = NodeConfig( nodeName="ClaudeAnthropic", processorType=self.processor_type, icon="AnthropicLogo", fields=fields, outputType="markdown", section="models", helpMessage="claudeAnthropichHelp", showHandlesNames=True, ) return config def handle_stream_awnser(self, awnser): event = ProcessorEvent(self, awnser) self.notify(EventType.STREAMING, event) def process(self): """ Retrieve max_tokens from a map instead of the node config. If 'thinking' is enabled and the model supports it, we choose a different max_tokens. """ prompt = self.get_input_by_name("prompt") prompt_context = self.get_input_by_name("context", None) model = self.get_input_by_name("model", "claude-3-5-sonnet-20240620") temperature = self.get_input_by_name("temperature", 1) thinking = self.get_input_by_name("thinking", False) if "3-7" not in model and "4-0" not in model: thinking = False budget_tokens = None if thinking: budget_tokens = self.get_input_by_name("budget_tokens", 1024) model_config = ClaudeAnthropicProcessor.model_config_map.get( model, ClaudeAnthropicProcessor.model_config_map["claude-3-5-sonnet-20240620"], ) if thinking: max_tokens = model_config["max_tokens_thinking"] else: max_tokens = model_config["max_tokens"] if prompt is None: return None api_key = self._processor_context.get_value("anthropic_api_key") if api_key is None: raise Exception("No Anthropic API key found") client = anthropic.Anthropic(api_key=api_key) awnser = "" if prompt_context is not None: messages = [ { "role": "user", "content": f"Context: {prompt_context} \n Prompt: {prompt}", } ] else: messages = [{"role": "user", "content": prompt}] stream_kwargs = { "model": model, "temperature": temperature, "max_tokens": max_tokens, "messages": messages, } if thinking: stream_kwargs["thinking"] = { "budget_tokens": budget_tokens, "type": "enabled", } with client.messages.stream(**stream_kwargs) as stream: try: current_block_type = None for event in stream: if event.type == "content_block_start": current_block_type = event.content_block.type elif event.type == "content_block_delta": if event.delta.type == "thinking_delta": self.reasoning_content += event.delta.thinking elif event.delta.type == "text_delta": awnser += event.delta.text self.handle_stream_awnser(awnser) elif event.type == "message_stop": break except Exception as e: logging.error(f"An error occurred during streaming : {e}") raise Exception("An error occurred during streaming") finally: stream.close() return awnser def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/deepseek_processor.py ================================================ from ...launcher.event_type import EventType from ...launcher.processor_event import ProcessorEvent from ...context.processor_context import ProcessorContext from ..model import Field, NodeConfig, Option from .extension_processor import ContextAwareExtensionProcessor from openai import OpenAI class DeepSeekProcessor(ContextAwareExtensionProcessor): processor_type = "deepseek-processor" streaming = True def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.reasoning_content = "" def get_node_config(self): context = Field( name="context", label="context", type="textfield", required=False, placeholder="ContextPlaceholder", hasHandle=True, ) text = Field( name="prompt", label="prompt", type="textarea", required=True, placeholder="PromptPlaceholder", hasHandle=True, ) model_options = [ Option( default=False, value="deepseek-chat", label="V3", ), Option( default=True, value="deepseek-reasoner", label="R1", ), ] model = Field( name="model", type="option", options=model_options, required=True, ) fields = [model, context, text] config = NodeConfig( nodeName="DeepSeek", processorType=self.processor_type, icon="DeepSeekLogo", fields=fields, outputType="text", section="models", helpMessage="deepSeekHelp", showHandlesNames=True, ) return config def process(self): prompt = self.get_input_by_name("prompt") context = self.get_input_by_name("context", "") model = self.get_input_by_name("model") if prompt is None: return None api_key = self._processor_context.get_value("deepseek_api_key") if api_key is None: raise Exception("No DeepSeek API key found") client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com") response = client.chat.completions.create( model=model, messages=[ { "role": "user", "content": f"{context} {prompt}", } ], stream=self.streaming, ) if self.streaming: final_response = "" for chunk in response: r_content = getattr(chunk.choices[0].delta, "reasoning_content", None) if r_content is not None: self.reasoning_content += r_content if not chunk.choices[0].delta.content: continue final_response += chunk.choices[0].delta.content event = ProcessorEvent(self, final_response) self.notify(EventType.STREAMING, event) return final_response return response.choices[0].message.content def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/document_to_text_processor.py ================================================ import logging from queue import Queue import requests from ....tasks.task_exception import TaskAlreadyRegisteredError from ..node_config_builder import FieldBuilder, NodeConfigBuilder from ....tasks.thread_pool_task_manager import add_task, register_task_processor from ....utils.processor_utils import ( create_temp_file_with_bytes_content, get_max_file_size_in_mb, is_accepted_url_file_size, is_s3_file, is_valid_url, ) from ....tasks.task_utils import wait_for_result from ..model import NodeConfig from .extension_processor import BasicExtensionProcessor from langchain.document_loaders import ( UnstructuredPDFLoader, UnstructuredHTMLLoader, CSVLoader, JSONLoader, TextLoader, PyMuPDFLoader, ) class DocumentToText(BasicExtensionProcessor): processor_type = "document-to-text-processor" WAIT_TIMEOUT = 60 def __init__(self, config): super().__init__(config) self.loaders = { "application/pdf": PyMuPDFLoader, "text/plain": TextLoader, "text/csv": CSVLoader, "text/html": UnstructuredHTMLLoader, "application/json": JSONLoader, } self.accepted_mime_types = self.loaders.keys() def get_node_config(self) -> NodeConfig: urlField = ( FieldBuilder() .set_name("document_url") .set_label("document_url") .set_type("textfield") .set_required(True) .set_placeholder("URLPlaceholder") .set_has_handle(True) .build() ) return ( NodeConfigBuilder() .set_node_name("DocumentToText") .set_processor_type(self.processor_type) .set_icon("FaFile") .set_section("input") .set_help_message("documentToTextHelp") .set_show_handles(True) .set_output_type("text") .set_default_hide_output(True) .add_field(urlField) .build() ) def get_loader_for_mime_type(self, mime_type, path): """Return an instance of the loader class associated with the given mime_type.""" loader_class = self.loaders.get(mime_type) if loader_class: return loader_class(file_path=path) else: return None def load_document(self, loader): results_queue = Queue() add_task("document_loader", loader, results_queue) document = None try: document = wait_for_result(results_queue) except TimeoutError as e: raise TimeoutError("Timeout - The document took too long to load") return document @staticmethod def document_loader_task(loader): return loader.load() def register_background_task(self): try: register_task_processor("document_loader", self.document_loader_task) except TaskAlreadyRegisteredError as e: pass def process(self): url = self.get_input_by_name("document_url") if not is_valid_url(url): raise ValueError("Invalid URL") if not is_s3_file(url) and not is_accepted_url_file_size(url): raise ValueError( f"File size is too large (Max : {get_max_file_size_in_mb()})" ) r = requests.get(url) if r.status_code != 200: raise ValueError( "Check the url of your file; returned status code %s" % r.status_code ) mime_type = r.headers.get("Content-Type") if not is_s3_file(url) and mime_type not in self.accepted_mime_types: raise ValueError("The file type is not supported.") temp_file, temp_dir = create_temp_file_with_bytes_content(r.content) file_path = str(temp_file) loader = self.get_loader_for_mime_type(mime_type, file_path) self.register_background_task() try: document = self.load_document(loader) if len(document) > 0: output = "" for doc in document: output += doc.page_content return output else: return None except Exception as e: logging.warning(f"Failed to load document from URL: {e}") raise e finally: temp_dir.cleanup() ================================================ FILE: packages/backend/app/processors/components/extension/extension_processor.py ================================================ from ..model import NodeConfig from ...context.processor_context import ProcessorContext from ..processor import BasicProcessor, ContextAwareProcessor class ExtensionProcessor: """Base interface for extension processors""" def get_node_config(self) -> NodeConfig: pass class DynamicExtensionProcessor: """Base interface for dynamic extension processors - These nodes config are populated by an API call after a user choice""" def get_dynamic_node_config(self, data) -> NodeConfig: pass class BasicExtensionProcessor(ExtensionProcessor, BasicProcessor): """A basic extension processor that does not depend on user-specific parameters. Inherits basic processing capabilities from BasicProcessor and schema handling from ExtensionProcessor. Args: config (dict): Configuration dictionary for processor setup. """ def __init__(self, config): super().__init__(config) class ContextAwareExtensionProcessor(ExtensionProcessor, ContextAwareProcessor): """An extension processor that requires context about the user, such as user-specific settings or keys. This class supports context-aware processing by incorporating user context into the processing flow. Args: config (dict): Configuration dictionary for processor setup. context (ProcessorContext, optional): Context object containing user-specific parameters. Defaults to None. """ def __init__(self, config, context: ProcessorContext = None): super().__init__(config) self._processor_context = context ================================================ FILE: packages/backend/app/processors/components/extension/generate_number_processor.py ================================================ import random from ..node_config_builder import FieldBuilder, NodeConfigBuilder from ...context.processor_context import ProcessorContext from .extension_processor import ( ContextAwareExtensionProcessor, DynamicExtensionProcessor, ) class GenerateNumberProcessor( ContextAwareExtensionProcessor, DynamicExtensionProcessor ): processor_type = "generate-number-processor" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def get_node_config(self): min_field = ( FieldBuilder() .set_name("min") .set_label("Min") .set_type("numericfield") .set_description("minimumValueForTheRandomNumber") .set_default_value(0) .build() ) max_field = ( FieldBuilder() .set_name("max") .set_label("Max") .set_type("numericfield") .set_description("maximumValueForTheRandomNumber") .set_default_value(1000) .build() ) return ( NodeConfigBuilder() .set_node_name("Generate Number") .set_processor_type(self.processor_type) .set_section("tools") .set_help_message("generateNumberHelp") .set_show_handles(True) .add_field(min_field) .add_field(max_field) .set_output_type("text") .set_icon("GiPerspectiveDiceSix") .build() ) def process(self): # Retrieve optional parameters; default values are used if they are not provided. min_val = self.get_input_by_name("min") max_val = self.get_input_by_name("max") try: min_val = int(min_val) if min_val is not None else 0 max_val = int(max_val) if max_val is not None else 500 except ValueError: raise ValueError("Both 'min' and 'max' should be valid numbers") if min_val > max_val: raise ValueError("'min' should not be greater than 'max'") # Generate and return a random number in the inclusive range [min_val, max_val] random_number = random.randint(min_val, max_val) return [random_number] def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/gpt_image_processor.py ================================================ import base64 import mimetypes import os import re from datetime import datetime from io import BytesIO from urllib.parse import unquote, urlparse import requests from openai import OpenAI from ...context.processor_context import ProcessorContext from ..model import Field, NodeConfig, Option from ..node_config_builder import NodeConfigBuilder from .extension_processor import ( ContextAwareExtensionProcessor, DynamicExtensionProcessor, ) class GPTImageProcessor(ContextAwareExtensionProcessor, DynamicExtensionProcessor): processor_type = "gpt-image-processor" # our two modes methods = ["generate", "edit"] def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.method = self.get_input_by_name("method") def get_node_config(self): # top-level mode selector method_options = [ Option(default=(m == "generate"), value=m, label=m.title()) for m in self.methods ] method_field = Field( name="method", label="mode", type="select", options=method_options, required=True, ) return ( NodeConfigBuilder() .set_node_name("GPT Image") .set_processor_type(self.processor_type) .set_icon("OpenAILogo") .set_help_message("gptImageHelp") .set_section("models") .add_field(method_field) .set_is_dynamic(True) .build() ) # — builders for each mode's fields — def build_generate_config(self, builder): # same fields as your original generate case builder.add_field( Field( name="model", label="Model", type="select", options=[ Option(default=True, value="gpt-image-1", label="gpt-image-1") ], required=True, ) ) builder.add_field( Field( name="prompt", label="Prompt", type="textarea", required=True, placeholder="InputTextPlaceholder", hasHandle=True, ) ) builder.add_field( Field( name="size", label="Size", type="select", options=[ Option(default=True, value="auto", label="auto"), Option(default=False, value="1024x1024", label="1024x1024"), Option(default=False, value="1536x1024", label="1536x1024"), Option(default=False, value="1024x1536", label="1024x1536"), ], required=True, ) ) builder.add_field( Field( name="quality", label="Quality", type="select", options=[ Option(default=True, value="auto", label="auto"), Option(default=False, value="low", label="low"), Option(default=False, value="medium", label="medium"), Option(default=False, value="high", label="high"), ], required=True, ) ) builder.add_field( Field( name="background", label="Background", type="select", options=[ Option(default=True, value="opaque", label="opaque"), Option(default=False, value="transparent", label="transparent"), ], required=True, ) ) builder.add_field( Field( name="moderation", label="Moderation", type="select", options=[ Option(default=False, value="auto", label="auto"), Option(default=True, value="low", label="low"), ], required=True, ) ) builder.set_output_type("imageUrl") def build_edit_config(self, builder): # same fields as your original edit case builder.add_field( Field( name="model", label="Model", type="select", options=[ Option(default=True, value="gpt-image-1", label="gpt-image-1") ], required=True, ) ) builder.add_field( Field( name="prompt", label="Prompt", type="textarea", required=True, placeholder="InputTextPlaceholder", hasHandle=True, ) ) builder.add_field( Field( name="mask", label="Mask", type="fileUpload", hasHandle=True, description="gptImageMaskDescription", ) ) builder.add_field( Field( name="image", label="Image", type="fileUpload", hasHandle=True, canAddChildrenFields=True, ) ) builder.set_output_type("imageUrl") method_config_builders = { "generate": build_generate_config, "edit": build_edit_config, } def get_dynamic_node_config(self, data) -> NodeConfig: method = data["method"] builder = ( NodeConfigBuilder() .set_node_name(f"GPT Image – {method.title()}") .set_processor_type(self.processor_type) .set_icon("OpenAILogo") .set_section("models") .set_show_handles(True) ) # inject the right fields self.method_config_builders[method](self, builder) return builder.build() @staticmethod def get_image_file_from_url(url): response = requests.get(url) response.raise_for_status() parsed = urlparse(url) filename = os.path.basename(parsed.path) or "image.png" filename = unquote(filename) if "." not in filename: ext = mimetypes.guess_extension(response.headers.get("Content-Type", "")) filename += ext or ".png" buf = BytesIO(response.content) buf.name = filename return buf def process(self): prompt = self.get_input_by_name("prompt") model = self.get_input_by_name("model") api_key = self._processor_context.get_value("openai_api_key") if api_key is None: raise Exception("No OpenAI API key found") client = OpenAI(api_key=api_key) if self.method == "edit": # gather all image_* fields just like before images_fields = [ f for f in self.fields_names if re.match(r"^image_\d+$", f) ] images_fields.insert(0, "image") urls = [self.get_input_by_name(fld, None) for fld in images_fields] urls = [u for u in urls if u] files = [GPTImageProcessor.get_image_file_from_url(u) for u in urls] mask = self.get_input_by_name("mask", None) if mask: mask = GPTImageProcessor.get_image_file_from_url(mask) result = client.images.edit( model=model, prompt=prompt, image=files, mask=mask, ) else: result = client.images.edit( model=model, prompt=prompt, image=files, ) else: # generate size = self.get_input_by_name("size") quality = self.get_input_by_name("quality") background = self.get_input_by_name("background") moderation = self.get_input_by_name("moderation") result = client.images.generate( model=model, prompt=prompt, size=size, quality=quality, background=background, moderation=moderation, ) img_b64 = result.data[0].b64_json img_bytes = base64.b64decode(img_b64) storage = self.get_storage() fname = f"{self.name}-{datetime.now():%Y%m%d%H%M%S%f}.png" return storage.save(fname, img_bytes) def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/http_get_processor.py ================================================ import logging import requests import json from urllib.parse import urlparse from ..node_config_builder import FieldBuilder, NodeConfigBuilder from ...context.processor_context import ProcessorContext from .extension_processor import ContextAwareExtensionProcessor class HttpGetProcessor(ContextAwareExtensionProcessor): processor_type = "http-get-processor" max_timeout = 5 # Maximum timeout in seconds max_response_size_in_mb = 2 max_response_size = ( 1024 * 1024 * max_response_size_in_mb ) # Maximum response size in bytes (2 MB) def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def get_node_config(self): url_field = ( FieldBuilder() .set_name("url") .set_label("URL") .set_type("textfield") .set_required(True) .set_placeholder("httpGetProcessorURLPlaceholder") .set_description("httpGetProcessorURLDescription") .set_has_handle(True) .build() ) headers_field = ( FieldBuilder() .set_name("headers") .set_label("Headers") .set_type("dictionnary") .set_description("httpGetProcessorHeadersDescription") .build() ) return ( NodeConfigBuilder() .set_node_name("HTTP Get") .set_processor_type(self.processor_type) .set_icon("TbHttpGet") .set_section("input") .set_help_message("httpGetProcessorHelp") .set_output_type("text") .set_show_handles(True) .add_field(url_field) .add_field(headers_field) .build() ) def convert_headers_array_to_json(self, headers_array): headers = {} for header in headers_array: headers[header["key"]] = header["value"] return json.dumps(headers) def process(self): url = self.get_input_by_name("url") headers = self.get_input_by_name("headers") timeout = self.get_input_by_name("timeout") if not url: raise ValueError("URL is required.") # Validate URL to prevent misuse parsed_url = urlparse(url) if not parsed_url.scheme.startswith("http"): raise ValueError("Invalid URL scheme. Only HTTP and HTTPS are allowed.") timeout = HttpGetProcessor.max_timeout if headers: headers = self.convert_headers_array_to_json(headers) try: headers = json.loads(headers) except json.JSONDecodeError: raise Exception("Headers must be a valid JSON.") else: headers = {} try: response = requests.get( url=url, headers=headers, timeout=timeout, allow_redirects=False, stream=True, ) response.raise_for_status() except requests.exceptions.RequestException as e: logging.warning(f"HTTP GET request failed: {str(e)}") raise Exception(f"HTTP GET request failed: {str(e)}") # Limit the response size content = bytes() total_size = 0 try: for chunk in response.iter_content(chunk_size=8192): content += chunk total_size += len(chunk) if total_size > HttpGetProcessor.max_response_size: logging.warning("Response size exceeds maximum allowed limit.") raise Exception( f"Response size exceeds maximum allowed limit of {HttpGetProcessor.max_response_size_in_mb} MB. If need to load file, consider using the file node in URL mode." ) finally: response.close() content_type = response.headers.get("Content-Type", "") if "application/json" in content_type: try: return [json.loads(content.decode(response.encoding or "utf-8"))] except ValueError: raise Exception("Failed to parse JSON response.") else: return content.decode(response.encoding or "utf-8", errors="replace") def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/open_router_processor.py ================================================ import logging from ....env_config import is_local_environment from ...launcher.event_type import EventType from ...launcher.processor_event import ProcessorEvent from ...context.processor_context import ProcessorContext from ..model import Field, NodeConfig, Option, Condition from .extension_processor import ContextAwareExtensionProcessor from openai import OpenAI import requests from cachetools import TTLCache, cached def load_models_from_file(): import json import os current_dir = os.path.dirname(os.path.abspath(__file__)) models_file_path = os.path.join( current_dir, "..", "..", "..", "..", "resources", "data", "openrouter_models.json", ) with open(models_file_path, "r") as file: models = json.load(file) return models.get("data", []) @cached(TTLCache(maxsize=1, ttl=120000)) def get_models(): """ Fetches the list of available models from OpenRouter API. Caches the result to avoid redundant API calls. """ url = "https://openrouter.ai/api/v1/models" try: response = requests.get(url, timeout=10) response.raise_for_status() models = response.json() return models.get("data", []) except Exception as e: logging.warning( f"Failed to fetch OpenRouter models - Loading from file instead: {e}" ) return load_models_from_file() @cached(TTLCache(maxsize=1, ttl=120000)) def get_text_to_image_model_ids(): """ Returns a list of model IDs that support text to image generation. """ available_models = get_models() text_image_model_ids = [ model["id"] for model in available_models if model.get("architecture").get("modality") == "text+image->text" ] return text_image_model_ids class OpenRouterProcessor(ContextAwareExtensionProcessor): processor_type = "openrouter-processor" streaming = True def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def get_node_config(self): context = Field( name="context", label="context", type="textfield", required=False, placeholder="ContextPlaceholder", hasHandle=True, ) text = Field( name="prompt", label="prompt", type="textarea", required=True, placeholder="PromptPlaceholder", hasHandle=True, ) available_models = get_models() target_default_model_id = "google/gemma-2-9b-it:free" model_options = [ Option( default=(model["id"] == target_default_model_id), value=model["id"], label=model.get("name", model["id"]), ) for model in available_models ] model_field = Field( name="model", label="model", type="select", options=model_options, required=True, ) text_image_model_ids = get_text_to_image_model_ids() image_url_condition = Condition( field="model", operator="in", value=text_image_model_ids ) image_url = Field( name="image_url", label="Image URL", type="textfield", placeholder="InputImagePlaceholder", hasHandle=True, condition=image_url_condition, ) fields = [model_field, image_url, context, text] config = NodeConfig( nodeName="OpenRouter", processorType=self.processor_type, icon="OpenRouterLogo", fields=fields, outputType="text", section="models", helpMessage="openRouterHelp", showHandlesNames=True, ) return config def process(self): prompt = self.get_input_by_name("prompt") context = self.get_input_by_name("context", "") model = self.get_input_by_name("model") image_url = self.get_input_by_name("image_url", None) if prompt is None: return None api_key = self._processor_context.get_value("openrouter_api_key") if api_key is None: raise Exception("No OpenRouter API key found") client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=api_key) text_image_model_ids = get_text_to_image_model_ids() if image_url is not None and model in text_image_model_ids: content = [ { "type": "image_url", "image_url": {"url": image_url}, }, {"type": "text", "text": prompt}, ] else: content = f"{context} {prompt}" response = client.chat.completions.create( model=model, messages=[{"role": "user", "content": content}], stream=self.streaming, ) if self.streaming: final_response = "" for chunk in response: if not chunk.choices[0].delta.content: continue final_response += chunk.choices[0].delta.content event = ProcessorEvent(self, final_response) self.notify(EventType.STREAMING, event) return final_response return response.choices[0].message.content def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/openai_reasoning_processor.py ================================================ import logging from app.processors.exceptions import LightException from ...launcher.event_type import EventType from ...launcher.processor_event import ProcessorEvent from ...context.processor_context import ProcessorContext from ..model import Field, FieldCondition, NodeConfig, Option from .extension_processor import ContextAwareExtensionProcessor from openai import OpenAI class OpenAIReasoningProcessor(ContextAwareExtensionProcessor): processor_type = "openai-reasoning-processor" streaming = True models_with_reasoning_effort = ["o3-mini", "o4-mini", "o3"] def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def get_node_config(self): context = Field( name="context", label="context", type="textfield", required=False, placeholder="ContextPlaceholder", hasHandle=True, ) text = Field( name="prompt", label="prompt", type="textarea", required=True, placeholder="PromptPlaceholder", hasHandle=True, ) model_options = [ Option( default=True, value="o4-mini", label="o4-mini", ), Option( default=False, value="o3-mini", label="o3-mini", ), Option( default=False, value="o3", label="o3", ), Option( default=False, value="o1-pro", label="o1-pro", ), Option( default=False, value="o1", label="o1", ), ] model = Field( name="model", type="option", options=model_options, required=True, ) reasoning_effort_options = [ Option( default=False, value="low", label="low", ), Option( default=True, value="medium", label="medium", ), Option( default=False, value="high", label="high", ), ] reasoning_effort = Field( name="reasoning_effort", label="reasoning_effort", type="select", options=reasoning_effort_options, condition=FieldCondition( field="model", operator="in", value=OpenAIReasoningProcessor.models_with_reasoning_effort, ), ) fields = [model, context, text, reasoning_effort] config = NodeConfig( nodeName="OpenAI o-series", processorType=self.processor_type, icon="OpenAILogo", fields=fields, outputType="text", section="models", helpMessage="openaio1Help", showHandlesNames=True, ) return config def handle_stream_answer(self, awnser): event = ProcessorEvent(self, awnser) self.notify(EventType.STREAMING, event) def process(self): prompt = self.get_input_by_name("prompt") context = self.get_input_by_name("context", "") model = self.get_input_by_name("model") reasoning_effort = self.get_input_by_name("reasoning_effort", "medium") if prompt is None: return None api_key = self._processor_context.get_value("openai_api_key") if api_key is None: raise Exception("No OpenAI API key found") client = OpenAI(api_key=api_key) kwargs = { "model": model, "input": [{"role": "user", "content": f"{context} {prompt}"}], "stream": self.streaming, } if model in OpenAIReasoningProcessor.models_with_reasoning_effort: kwargs["reasoning"] = {"effort": reasoning_effort} stream = client.responses.create(**kwargs) final_response = "" for event in stream: type = event.type if type == "response.output_text.delta": final_response += event.delta self.handle_stream_answer(final_response) if type == "response.completed": response_data = event.response final_response = response_data.output_text if type == "response.failed": response_data = event.response if not hasattr(response_data, "error"): logging.warning(f"Error from OpenAI with no data: {response_data}") continue raise LightException( f"Error from OpenAI : {response_data.error.message}" ) if type == "error": raise LightException(f"Error from OpenAI : {event.message}") return final_response def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/openai_text_to_speech_processor.py ================================================ import logging import re from ...context.processor_context import ProcessorContext from ..model import Field, NodeConfig, Option, Condition from .extension_processor import ContextAwareExtensionProcessor from openai import OpenAI from datetime import datetime import io from pydub import AudioSegment import eventlet class OpenAITextToSpeechProcessor(ContextAwareExtensionProcessor): processor_type = "openai-text-to-speech-processor" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) def get_node_config(self): text = Field( name="text", label="text", type="textfield", required=True, placeholder="InputTextPlaceholder", hasHandle=True, ) voices_options = [ Option( default=True, value="alloy", label="alloy", ), Option( default=False, value="ash", label="ash", ), Option( default=False, value="ballad", label="ballad", ), Option( default=False, value="coral", label="coral", ), Option( default=False, value="echo", label="echo", ), Option( default=False, value="fable", label="fable", ), Option( default=False, value="onyx", label="onyx", ), Option( default=False, value="nova", label="nova", ), Option( default=False, value="sage", label="sage", ), Option( default=False, value="shimmer", label="shimmer", ), ] voice = Field( name="voice", label="voice", type="select", options=voices_options, required=True, ) model_options = [ Option( default=True, value="gpt-4o-mini-tts", label="gpt-4o-mini-tts", ), Option( default=False, value="tts-1", label="tts-1", ), Option( default=False, value="tts-1-hd", label="tts-1-hd", ), ] model = Field( name="model", label="model", type="select", options=model_options, required=True, ) instructions_enabled_condition = Condition( field="model", operator="equals", value="gpt-4o-mini-tts" ) instructions = Field( name="instruction", label="instruction", type="textfield", required=False, placeholder="TTSInstructionPlaceholder", description="TTSInstructionDescription", hasHandle=True, condition=instructions_enabled_condition, ) fields = [text, model, voice, instructions] config = NodeConfig( nodeName="TextToSpeech", processorType=self.processor_type, icon="OpenAILogo", fields=fields, outputType="audioUrl", section="models", helpMessage="textToSpeechHelp", showHandlesNames=True, keywords=["Audio", "Speech", "OpenAI", "TTS"], ) return config def split_text_into_chunks(text, max_length=4096): """ Split text into chunks of up to max_length characters by packing as many whole sentences as possible. If a single sentence exceeds max_length, split it into smaller parts. """ # Split text by sentence-ending punctuation followed by whitespace. sentences = re.split(r"(?<=[.!?])\s+", text) chunks = [] current_sentences = [] current_length = 0 for sentence in sentences: sentence_length = len(sentence) # Add a space if there is already a sentence in the current chunk. additional_length = ( sentence_length if not current_sentences else sentence_length + 1 ) if current_length + additional_length <= max_length: # Append sentence to the current chunk. current_sentences.append(sentence) current_length += additional_length else: # Flush the current chunk if it's not empty. if current_sentences: chunks.append(" ".join(current_sentences)) current_sentences = [] current_length = 0 # If the sentence itself is too long, split it into parts. if sentence_length > max_length: parts = [ sentence[i : i + max_length] for i in range(0, sentence_length, max_length) ] # All full parts are separate chunks. chunks.extend(parts[:-1]) # The last part might be less than max_length; add it to current chunk. current_sentences = [parts[-1]] current_length = len(parts[-1]) else: # Start a new chunk with the sentence. current_sentences = [sentence] current_length = sentence_length if current_sentences: chunks.append(" ".join(current_sentences)) return chunks def process(self): text = self.get_input_by_name("text") voice = self.get_input_by_name("voice") model = self.get_input_by_name("model") instruction = self.get_input_by_name("instruction", None) if text is None: return None api_key = self._processor_context.get_value("openai_api_key") if api_key is None: raise Exception("No OpenAI API key found") client = OpenAI(api_key=api_key) # Split text into chunks that are each less than or equal to 4096 characters. chunks = OpenAITextToSpeechProcessor.split_text_into_chunks(text, 4096) pool = eventlet.GreenPool(2) def create_audio_segment(chunk): kwargs = { "model": model, "voice": voice, "input": chunk, } if instruction is not None: kwargs["instructions"] = instruction response = client.audio.speech.create(**kwargs) if response is None: return None # Convert the response content (mp3 bytes) into an AudioSegment. return AudioSegment.from_file(io.BytesIO(response.content), format="mp3") # Process chunks concurrently; imap preserves the order of chunks. audio_segments = list(pool.imap(create_audio_segment, chunks)) # Filter out any None segments. audio_segments = [segment for segment in audio_segments if segment is not None] if not audio_segments: return None # Merge the audio segments. merged_audio = audio_segments[0] for seg in audio_segments[1:]: merged_audio += seg # Export merged audio to a bytes buffer. merged_audio_buffer = io.BytesIO() merged_audio.export(merged_audio_buffer, format="mp3") merged_audio_buffer.seek(0) storage = self.get_storage() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") filename = f"{self.name}-{timestamp_str}.mp3" url = storage.save(filename, merged_audio_buffer.read()) # cleanup merged_audio_buffer.close() del merged_audio_buffer del merged_audio del audio_segments return url def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/replace_text_processor.py ================================================ import logging import re from ..node_config_builder import FieldBuilder, NodeConfigBuilder from .extension_processor import BasicExtensionProcessor from ..core.processor_type_name_utils import ProcessorType class ReplaceTextProcessor(BasicExtensionProcessor): processor_type = ProcessorType.REPLACE_TEXT def __init__(self, config): super().__init__(config) def get_node_config(self): input_text_field = ( FieldBuilder() .set_name("input_text") .set_label("Input Text") .set_type("textarea") .set_required(True) .set_placeholder("ReplaceTextInputPlaceholder") .set_has_handle(True) .build() ) search_text_field = ( FieldBuilder() .set_name("search_text") .set_label("Search Text") .set_type("textfield") .set_required(True) .set_placeholder("ReplaceTextSearchPlaceholder") .set_has_handle(True) .build() ) replacement_text_field = ( FieldBuilder() .set_name("replacement_text") .set_label("Replacement Text") .set_type("textfield") .set_required(True) .set_placeholder("ReplaceTextReplacePlaceholder") .set_has_handle(True) .build() ) replace_all_field = ( FieldBuilder() .set_name("replace_all") .set_label("Replace All Occurrences") .set_type("boolean") .set_default_value(True) .build() ) use_regex_field = ( FieldBuilder() .set_name("use_regex") .set_label("Use Regular Expression") .set_type("boolean") .set_default_value(False) .build() ) case_sensitivity_field = ( FieldBuilder() .set_name("case_sensitivity") .set_label("Case Sensitive") .set_type("boolean") .set_default_value(True) .build() ) return ( NodeConfigBuilder() .set_node_name("ReplaceText") .set_processor_type(self.processor_type.value) .set_section("tools") .set_help_message("replaceTextNodeHelp") .set_show_handles(True) .set_output_type("text") .set_default_hide_output(False) .add_field(input_text_field) .add_field(search_text_field) .add_field(replacement_text_field) .add_field(replace_all_field) .add_field(case_sensitivity_field) .add_field(use_regex_field) .set_icon("MdSwapHoriz") .build() ) def process(self): input_text = self.get_input_by_name("input_text") search_text = self.get_input_by_name("search_text") replacement_text = self.get_input_by_name("replacement_text") replace_all = self.get_input_by_name("replace_all") use_regex = self.get_input_by_name("use_regex") case_sensitivity = self.get_input_by_name("case_sensitivity") flags = 0 if not case_sensitivity: flags |= re.IGNORECASE if use_regex: try: pattern = re.compile(search_text, flags) count = 0 if replace_all else 1 result_text = pattern.sub(replacement_text, input_text, count=count) except re.error as e: logging.warning(f"Invalid regular expression: {e}") result_text = input_text else: if not case_sensitivity: escaped_search_text = re.escape(search_text) pattern = re.compile(escaped_search_text, flags) count = 0 if replace_all else 1 result_text = pattern.sub(replacement_text, input_text, count=count) else: if replace_all: result_text = input_text.replace(search_text, replacement_text) else: result_text = input_text.replace(search_text, replacement_text, 1) return [result_text] ================================================ FILE: packages/backend/app/processors/components/extension/stabilityai_generic_processor.py ================================================ import json import logging import os from ..node_config_utils import get_sub_configuration from ....utils.openapi_client import Client from ....utils.processor_utils import ( stream_download_file_as_binary, ) from ....utils.openapi_converter import OpenAPIConverter from ....utils.openapi_reader import OpenAPIReader from ..node_config_builder import FieldBuilder, NodeConfigBuilder from ...context.processor_context import ProcessorContext from ..model import NodeConfig, Option from .extension_processor import ( ContextAwareExtensionProcessor, DynamicExtensionProcessor, ) from datetime import datetime import re class StabilityAIGenericProcessor( ContextAwareExtensionProcessor, DynamicExtensionProcessor ): processor_type = "stabilityai-generic-processor" openapi_file_path = "./resources/openapi/stabilityai.json" paths_denied = [ re.compile(r"/v1/"), # Contains'/v1/' re.compile(r"/user/"), # Contains 'user' re.compile(r"/engines/"), # Contains 'engines' re.compile(r"/result/"), # Contains 'result' re.compile(r"/v2alpha/"), # Contains 'v2alpha' re.compile(r"/result"), # Temporary re.compile(r"/chat"), # api returns 404 for now ] api_reader = None all_paths_cache = None pooling_paths_cache = None allowed_paths_cache = None def __init__(self, config, context: ProcessorContext): super().__init__(config, context) if StabilityAIGenericProcessor.allowed_paths_cache is None: StabilityAIGenericProcessor.initialize_allowed_paths_cache() self.api_host = os.getenv( "STABLE_DIFFUSION_STABILITYAI_API_HOST", "https://api.stability.ai" ) self.path = self.get_input_by_name("path") self.initialize_api_config() self.final_node_config = self.get_dynamic_node_config(dict(path=self.path)) @classmethod def initialize_allowed_paths_cache(cls): cls.api_reader = OpenAPIReader(StabilityAIGenericProcessor.openapi_file_path) paths_names = cls.api_reader.get_all_paths_names() cls.all_paths_cache = paths_names cls.pooling_paths_cache = [path for path in paths_names if "/result/" in path] cls.allowed_paths_cache = [ path for path in paths_names if not cls.is_path_banned(path, cls.paths_denied) ] @staticmethod def is_path_banned(path, denied_patterns): return any(pattern.search(path) for pattern in denied_patterns) @staticmethod def get_pooling_path(path_selected): for path in StabilityAIGenericProcessor.pooling_paths_cache: if path.startswith(path_selected): return path return None def transform_path_options_labels(options): transformed_options = [] for option in options: # Remove the first path element and split the rest parts = re.sub(r"^/[^/]+/", "", option.label).split("/") # Take the last two elements, or one if alone if len(parts) > 1: label = f"{parts[-2].capitalize()} - {parts[-1].replace('-', ' ').capitalize()}" else: label = parts[-1].replace("-", " ").capitalize() transformed_option = Option( default=option.default, value=option.value, label=label ) transformed_options.append(transformed_option) transformed_options.sort(key=lambda option: option.label) return transformed_options def get_node_config(self): if StabilityAIGenericProcessor.allowed_paths_cache is None: StabilityAIGenericProcessor.initialize_allowed_paths_cache() path_options = [ Option(default=False, value=name, label=name) for i, name in enumerate(StabilityAIGenericProcessor.allowed_paths_cache) ] path_options = self.transform_path_options_labels(path_options) path_options[0].default = True path = ( FieldBuilder() .set_name("path") .set_label("Path") .set_type("select") .set_options(path_options) .build() ) return ( NodeConfigBuilder() .set_node_name("StabilityAI") .set_processor_type(self.processor_type) .set_icon("StabilityAILogo") .set_section("models") .set_help_message("stableDiffusionPromptHelp") .set_show_handles(True) .add_field(path) .set_is_dynamic(True) # Important .build() ) def initialize_api_config(self): response_content_path = self.path response_method = "post" self.path_accept = StabilityAIGenericProcessor.api_reader.get_path_accept( self.path, "post" ) self.pooling_path = self.get_pooling_path(self.path) if self.pooling_path is not None: response_content_path = self.pooling_path response_method = "get" self.pooling_path_accept = ( StabilityAIGenericProcessor.api_reader.get_path_accept( self.pooling_path, "get" ) ) self.response_content_type = ( StabilityAIGenericProcessor.api_reader.get_response_content_type( response_content_path, response_method )[0] ) print(f"Response content type {self.response_content_type}") @staticmethod def determine_output_type(path_accept): if path_accept is None: return None elif path_accept == "video/*": return "videoUrl" elif "model" in path_accept: return "3dUrl" else: return "imageUrl" def get_dynamic_node_config(self, data) -> NodeConfig: if StabilityAIGenericProcessor.allowed_paths_cache is None: StabilityAIGenericProcessor.initialize_allowed_paths_cache() selected_api_path = data["path"] schema = StabilityAIGenericProcessor.api_reader.get_request_schema_for_path( selected_api_path, "post" ) path_accept = StabilityAIGenericProcessor.api_reader.get_path_accept( selected_api_path, "post" ) output_type = StabilityAIGenericProcessor.determine_output_type(path_accept) pooling_path = self.get_pooling_path(selected_api_path) if pooling_path is not None: pooling_path_accept = ( StabilityAIGenericProcessor.api_reader.get_path_accept( pooling_path, "get" ) ) output_type = StabilityAIGenericProcessor.determine_output_type( pooling_path_accept ) builder = OpenAPIConverter().convert_schema_to_node_config(schema) path_components = selected_api_path.split("/") last_component = ( path_components[-1] if path_components[-1] else path_components[-2] ) node_name = " ".join(word.capitalize() for word in last_component.split("-")) ( builder.set_node_name(f"StabilityAI - {node_name}") .set_processor_type(self.processor_type) .set_icon("StabilityAILogo") .set_section("models") .set_help_message("stableDiffusionPromptHelp") .set_show_handles(True) ) if output_type is not None: builder.set_output_type(output_type) return builder.build() def perform_pooling(self, client, path): return client.pooling(path=path, accept=self.pooling_path_accept) def prepare_and_process_response(self, response): storage = self.get_storage() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") extension = self.get_input_by_name("output_format") if extension: filename = f"{self.name}-{timestamp_str}.{extension}" else: if "gltf-binary" in self.response_content_type: extension = "glb" else: extension = self.response_content_type.split("/")[-1] filename = f"{self.name}-{timestamp_str}.{extension}" url = storage.save(filename, response) return url def get_fields_from_config(self): if self.final_node_config is None: return [] if isinstance(self.final_node_config, NodeConfig): return self.final_node_config.fields discriminators_values = [] for discriminator_name in self.final_node_config.discriminatorFields: value = self.get_input_by_name(discriminator_name) discriminators_values.append(value) corresponding_config = get_sub_configuration( discriminators_values, self.final_node_config ) if corresponding_config is None: return [] return corresponding_config.config.fields def quick_filter(self, data): if "mode" in data: if data["mode"] == "image-to-image": if "aspect_ratio" in data: del data["aspect_ratio"] if data["mode"] == "text-to-image": if "strength" in data: del data["strength"] if "image" in data: del data["image"] def process(self): api_key = self._processor_context.get_value("stabilityai_api_key") fields = self.get_fields_from_config() data = {field.name: self.get_input_by_name(field.name) for field in fields} self.quick_filter(data) binaryFieldNames = [field.name for field in fields if field.isBinary] files = {} if len(binaryFieldNames) > 0 else {"none": (None, "")} for field_name in binaryFieldNames: if field_name not in data: files[field_name] = None continue url = data[field_name] data[field_name] = None del data[field_name] if url: files[field_name] = stream_download_file_as_binary(url) else: files[field_name] = None client = Client( api_token=api_key, base_url=self.api_host, ) response = client.post( path=self.path, data=data, files=files, accept=self.path_accept ) if self.pooling_path: response_str = response.decode("utf-8") response_json = json.loads(response_str) key_name = "id" key_value = response_json[key_name] updated_pooling_path = self.pooling_path.replace( "{" + key_name + "}", str(key_value) ) response = self.perform_pooling(client, updated_pooling_path) return self.prepare_and_process_response(response) def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/extension/stable_diffusion_three_processor.py ================================================ import logging import os import requests from ..node_config_builder import FieldBuilder, NodeConfigBuilder from ...context.processor_context import ProcessorContext from ..model import Option from .extension_processor import ContextAwareExtensionProcessor from datetime import datetime class StableDiffusionThreeProcessor(ContextAwareExtensionProcessor): processor_type = "stabilityai-stable-diffusion-3-processor" def __init__(self, config, context: ProcessorContext): super().__init__(config, context) self.api_host = os.getenv( "STABLE_DIFFUSION_STABILITYAI_API_HOST", "https://api.stability.ai" ) def get_node_config(self): prompt = ( FieldBuilder() .set_name("prompt") .set_label("Prompt") .set_type("textfield") .set_required(True) .set_placeholder("GenericPromptPlaceholder") .set_has_handle(True) .build() ) negative_prompt = ( FieldBuilder() .set_name("negative_prompt") .set_label("Negative Prompt") .set_type("textfield") .set_placeholder("GenericNegativePromptPlaceholder") .set_has_handle(True) .build() ) model_options = [ Option( default=True, value="sd3.5-large", label="Stable Diffusion 3.5 Large" ), Option( default=False, value="sd3.5-large-turbo", label="Stable Diffusion 3.5 Large Turbo", ), Option(default=False, value="sd3-large", label="Stable Diffusion 3 Large"), Option( default=False, value="sd3-medium", label="Stable Diffusion 3 Medium" ), Option( default=False, value="sd3-large-turbo", label="Stable Diffusion 3 Large Turbo", ), ] model = ( FieldBuilder() .set_name("model") .set_label("Model") .set_type("select") .set_options(model_options) .build() ) aspect_ratio_options = [ Option(default=True, value="1:1", label="1:1"), Option(default=False, value="16:9", label="16:9"), Option(default=False, value="3:2", label="3:2"), Option(default=False, value="2:3", label="2:3"), Option(default=False, value="4:5", label="4:5"), Option(default=False, value="5:4", label="5:4"), Option(default=False, value="9:16", label="9:16"), Option(default=False, value="9:21", label="9:21"), Option(default=False, value="21:9", label="21:9"), ] aspect_ratio = ( FieldBuilder() .set_name("aspect_ratio") .set_label("Aspect Ratio") .set_type("select") .set_options(aspect_ratio_options) .build() ) seed = ( FieldBuilder() .set_name("seed") .set_label("Seed") .set_type("numericfield") .set_placeholder("Enter a numeric seed") .set_default_value(0) .set_has_handle(True) .build() ) return ( NodeConfigBuilder() .set_node_name("Stable Diffusion 3.5") .set_processor_type(self.processor_type) .set_icon("StabilityAILogo") .set_section("models") .set_help_message("stableDiffusionPromptHelp") .set_output_type("imageUrl") .set_show_handles(True) .add_field(prompt) .add_field(negative_prompt) .add_field(model) .add_field(aspect_ratio) .add_field(seed) .build() ) def process(self): prompt = self.get_input_by_name("prompt") model = self.get_input_by_name("model") seed = self.get_input_by_name("seed") aspect_ratio = self.get_input_by_name("aspect_ratio") negative_prompt = self.get_input_by_name("negative_prompt") if prompt is None: return None api_key = self._processor_context.get_value("stabilityai_api_key") data_to_send = { "prompt": prompt, "negative_prompt": negative_prompt if model != "sd3-turbo" else None, "model": model, "seed": seed, "aspect_ratio": aspect_ratio, } response = requests.post( f"{self.api_host}/v2beta/stable-image/generate/sd3", headers={ "Accept": "image/*", "Authorization": f"Bearer {api_key}", }, files={"none": ""}, data=data_to_send, ) return self.prepare_and_process_response(response) def prepare_and_process_response(self, response): if response.status_code != 200: logging.warning( f"API call to StabilityAI failed with status {response.status_code}: {response.text}" ) logging.warning("User prompt : " + self.get_input_by_name("prompt") or "") raise Exception(f"Error message from StabilityAI : \n {response.text}") storage = self.get_storage() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") filename = f"{self.name}-{timestamp_str}.png" url = storage.save(filename, response.content) return url def cancel(self): pass ================================================ FILE: packages/backend/app/processors/components/model.py ================================================ # generated by datamodel-codegen: # filename: schema.json # timestamp: 2025-05-26T04:44:24+00:00 from __future__ import annotations from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, RootModel from typing_extensions import Literal class Model(RootModel[Any]): root: Any class FieldType( RootModel[ Literal[ 'boolean', 'dictionnary', 'fileUpload', 'imageMaskCreator', 'input', 'inputInt', 'inputNameBar', 'json', 'list', 'nonRendered', 'numericfield', 'option', 'select', 'slider', 'switch', 'textToDisplay', 'textarea', 'textfield', ] ] ): root: Literal[ 'boolean', 'dictionnary', 'fileUpload', 'imageMaskCreator', 'input', 'inputInt', 'inputNameBar', 'json', 'list', 'nonRendered', 'numericfield', 'option', 'select', 'slider', 'switch', 'textToDisplay', 'textarea', 'textfield', ] class Operator( RootModel[ Literal[ 'equals', 'exists', 'greater than', 'in', 'less than', 'not equals', 'not exists', 'not in', ] ] ): root: Literal[ 'equals', 'exists', 'greater than', 'in', 'less than', 'not equals', 'not exists', 'not in', ] class Option(BaseModel): default: Optional[bool] = None label: Optional[str] = None value: Optional[str] = None class OutputType( RootModel[ Literal[ '3dUrl', 'audioUrl', 'fileUrl', 'imageBase64', 'imageUrl', 'markdown', 'pdfUrl', 'text', 'videoUrl', ] ] ): root: Literal[ '3dUrl', 'audioUrl', 'fileUrl', 'imageBase64', 'imageUrl', 'markdown', 'pdfUrl', 'text', 'videoUrl', ] class SectionType(RootModel[Literal['image-generation', 'input', 'models', 'tools']]): root: Literal['image-generation', 'input', 'models', 'tools'] class Condition(BaseModel): field: Optional[str] = None operator: Optional[Operator] = None value: Optional[Any] = None class ConditionGroup(BaseModel): conditions: Optional[List[Condition]] = None logic: Optional[Literal['AND', 'OR']] = None class FieldCondition(RootModel[Union[Condition, ConditionGroup]]): root: Union[Condition, ConditionGroup] class OmitNodeConfigFieldsOutputType(BaseModel): defaultHideOutput: Optional[bool] = None hasInputHandle: Optional[bool] = None helpMessage: Optional[str] = None hideFieldsIfParent: Optional[bool] = None icon: Optional[str] = None inputNames: Optional[List[str]] = None isBeta: Optional[bool] = None isDynamicallyGenerated: Optional[bool] = None nodeName: Optional[str] = None processorType: Optional[str] = None section: Optional[SectionType] = None showHandlesNames: Optional[bool] = None class Field(BaseModel): allowDecimal: Optional[bool] = None associatedField: Optional[str] = None canAddChildrenFields: Optional[bool] = None condition: Optional[FieldCondition] = None defaultValue: Optional[Any] = None description: Optional[str] = None hasHandle: Optional[bool] = None hidden: Optional[bool] = None hideIfParent: Optional[bool] = None isBinary: Optional[bool] = None isChild: Optional[bool] = None isLinked: Optional[bool] = None label: Optional[str] = None max: Optional[float] = None min: Optional[float] = None name: Optional[str] = None options: Optional[List[Option]] = None placeholder: Optional[str] = None required: Optional[bool] = None step: Optional[float] = None type: Optional[FieldType] = None withModalEdit: Optional[bool] = None class NodeConfig(BaseModel): defaultHideOutput: Optional[bool] = None fields: Optional[List[Field]] = None hasInputHandle: Optional[bool] = None helpMessage: Optional[str] = None hideFieldsIfParent: Optional[bool] = None icon: Optional[str] = None inputNames: Optional[List[str]] = None isBeta: Optional[bool] = None isDynamicallyGenerated: Optional[bool] = None nodeName: Optional[str] = None outputType: Optional[OutputType] = None processorType: Optional[str] = None section: Optional[SectionType] = None showHandlesNames: Optional[bool] = None class DiscriminatedNodeConfig(BaseModel): config: Optional[NodeConfig] = None discriminators: Optional[Dict[str, str]] = None class NodeSubConfig(BaseModel): discriminatorFields: Optional[List[str]] = None subConfigurations: Optional[List[DiscriminatedNodeConfig]] = None class NodeConfigVariant(NodeSubConfig, OmitNodeConfigFieldsOutputType): pass ================================================ FILE: packages/backend/app/processors/components/node_config_builder.py ================================================ from typing import Dict, List, Optional, Union from .model import ( DiscriminatedNodeConfig, Field, FieldType, NodeConfig, NodeConfigVariant, Option, OutputType, SectionType, ) class BaseNodeConfigBuilder: def __init__(self): self.nodeName: Optional[str] = None self.processorType: Optional[str] = None self.icon: Optional[str] = None self.outputType: Optional[str] = None self.section: Optional[str] = None self.helpMessage: Optional[str] = None self.showHandlesNames: Optional[bool] = False self.isBeta: Optional[bool] = False self.defaultHideOutput: Optional[bool] = False def set_node_name(self, name: str) -> "BaseNodeConfigBuilder": self.nodeName = name return self def set_processor_type(self, processor_type: str) -> "BaseNodeConfigBuilder": self.processorType = processor_type return self def set_icon(self, icon: str) -> "BaseNodeConfigBuilder": self.icon = icon return self def set_output_type(self, output_type: str) -> "BaseNodeConfigBuilder": self.outputType = OutputType(root=output_type) return self def set_section(self, section: str) -> "BaseNodeConfigBuilder": self.section = SectionType(root=section) return self def set_help_message(self, help_message: str) -> "BaseNodeConfigBuilder": self.helpMessage = help_message return self def set_show_handles(self, show: bool) -> "BaseNodeConfigBuilder": self.showHandlesNames = show return self def set_is_beta(self, beta: bool) -> "NodeConfigBuilder": self.isBeta = beta return self def set_default_hide_output(self, hide: bool) -> "NodeConfigBuilder": self.defaultHideOutput = hide return self class NodeConfigBuilder(BaseNodeConfigBuilder): def __init__(self): super().__init__() self.fields: List[Field] = [] self.isDynamicallyGenerated: Optional[bool] = False self.discriminators: Optional[Dict[str, str]] = None def set_is_dynamic(self, dyna: bool) -> "NodeConfigBuilder": self.isDynamicallyGenerated = dyna return self def set_fields(self, fields: List[Field]) -> "NodeConfigBuilder": self.fields = fields return self def add_field(self, field: Field) -> "NodeConfigBuilder": self.fields.append(field) return self def add_discriminator(self, key, value) -> "NodeConfigBuilder": if self.discriminators is None: self.discriminators = {} self.discriminators[key] = value return self def build(self) -> NodeConfig: baseConfig = NodeConfig( nodeName=self.nodeName, processorType=self.processorType, icon=self.icon, fields=self.fields, outputType=self.outputType, section=self.section, helpMessage=self.helpMessage, showHandlesNames=self.showHandlesNames, isDynamicallyGenerated=self.isDynamicallyGenerated, isBeta=self.isBeta, defaultHideOutput=self.defaultHideOutput, ) if self.discriminators is not None: return DiscriminatedNodeConfig( config=baseConfig, discriminators=self.discriminators ) else: return baseConfig class NodeConfigVariantBuilder(BaseNodeConfigBuilder): def __init__(self): super().__init__() self.subConfigurations: List[NodeConfig] = [] self.discriminatorFields: Optional[List[str]] = [] def add_discriminator_field(self, field: str) -> "NodeConfigVariantBuilder": if self.discriminatorFields is None: self.discriminatorFields = [] self.discriminatorFields.append(field) return self def add_sub_configuration( self, sub_configuration: NodeConfig ) -> "NodeConfigVariantBuilder": self.subConfigurations.append(sub_configuration) return self def build(self) -> NodeConfigVariant: for subConfig in self.subConfigurations: config = subConfig.config config.showHandlesNames = self.showHandlesNames config.icon = self.icon config.nodeName = self.nodeName config.outputType = self.outputType config.section = self.section config.processorType = self.processorType config.helpMessage = self.helpMessage return NodeConfigVariant( subConfigurations=self.subConfigurations, discriminatorFields=self.discriminatorFields, ) class FieldBuilder: def __init__(self): self._field = Field() def set_name(self, name: str) -> "FieldBuilder": self._field.name = name return self def set_label(self, label: str) -> "FieldBuilder": self._field.label = label return self def set_description(self, description: str) -> "FieldBuilder": self._field.description = description return self def set_type(self, field_type: str) -> "FieldBuilder": self._field.type = FieldType(root=field_type) return self def set_min(self, min: float) -> "FieldBuilder": self._field.min = min return self def set_max(self, max: float) -> "FieldBuilder": self._field.max = max return self def set_is_binary(self, binary: bool) -> "FieldBuilder": self._field.isBinary = binary return self def set_placeholder(self, placeholder: str) -> "FieldBuilder": self._field.placeholder = placeholder return self def set_required(self, required: bool) -> "FieldBuilder": self._field.required = required return self def set_options(self, options: List[Option]) -> "FieldBuilder": self._field.options = options return self def add_option(self, option: Option) -> "FieldBuilder": if not self._field.options: self._field.options = [] self._field.options.append(option) return self def set_default_value(self, default_value: Union[str, float]) -> "FieldBuilder": self._field.defaultValue = default_value return self def set_has_handle(self, has_handle: bool) -> "FieldBuilder": self._field.hasHandle = has_handle return self def build(self) -> Field: return self._field ================================================ FILE: packages/backend/app/processors/components/node_config_utils.py ================================================ from .model import NodeConfigVariant def get_sub_configuration(discriminators_values, node_config: NodeConfigVariant): for subconfig in node_config.subConfigurations: subconfig_discriminator_values = [ subconfig.discriminators[discriminator] for discriminator in subconfig.discriminators ] if subconfig_discriminator_values == discriminators_values: return subconfig ================================================ FILE: packages/backend/app/processors/components/processor.py ================================================ from abc import ABC, abstractmethod import json import logging from typing import Any, List, Optional, TypedDict, Union, Dict from ..launcher.processor_event import ProcessorEvent from ..launcher.event_type import EventType from ..observer.observer import Observer from .core.processor_type_name_utils import ProcessorType from ...storage.storage_strategy import StorageStrategy from ..context.processor_context import ProcessorContext class BadKeyInputIndex(Exception): """Exception raised for index out of bounds in the output list.""" def __init__(self, message="This input key does not exists"): self.message = message super().__init__(self.message) class InputItem(TypedDict, total=False): inputName: Optional[str] inputNode: str inputNodeOutputKey: int class Processor(ABC): processor_type: Optional["ProcessorType"] = None """The type of the processor""" observers: List[Observer] = [] """The observers of the processor""" storage_strategy: Optional["StorageStrategy"] """The storage strategy used by the processor""" _processor_context: Optional["ProcessorContext"] """The context data of the processor""" name: str """The name of the processor""" _output: Optional[Any] """The output of the processor""" inputs: Optional[List[InputItem]] """A list of inputs accepted by the processor.""" input_processors: List["Processor"] """The processors set as inputs""" is_processing: bool """Flag indicating if the processor has started working, useful when using API with cold start""" is_finished: bool """Flag indicating if the processor's has produced his output""" _has_dynamic_behavior: bool """Flag indicating if the processor's behavior and execution time are unpredictable and subject to change at runtime.""" def __init__(self, config: Dict[str, Any]) -> None: self.name = config["name"] self.processor_type = config["processorType"] self.observers = [] self._output = None self.inputs = None self._processor_context = None self.input_processors = [] self.storage_strategy = None self.is_finished = False self._has_dynamic_behavior = False self._config = config if ( config.get("config") is not None and config.get("config").get("fields") is not None and config.get("config").get("fields") != [] ): self.fields = config.get("config").get("fields") self.fields_names = [field["name"] for field in self.fields] if config.get("inputs") is not None and config.get("inputs") != []: self.inputs = config.get("inputs") def cleanup(self) -> None: self.input_processors = None self._processor_context = None self._output = None self.storage_strategy = None def process_and_update(self): output = self.process() if output is not None: self.set_output(output) return output @abstractmethod def process(self): pass @abstractmethod def cancel(self) -> None: pass def add_observer(self, observer): self.observers.append(observer) def remove_observer(self, observer): self.observers.remove(observer) if len(self.observers) == 0: self.observers = None return self.observers def notify(self, event: EventType, data: ProcessorEvent): for observer in self.observers: observer.notify(event, data) def get_output(self, input_key=None) -> Optional[str]: output = getattr(self, "_output", None) if output is not None and isinstance(output, list) and len(output) > 0: if input_key is not None: if input_key < 0 or input_key >= len(output): logging.warning( f"Index {input_key} out of bounds for output of size {len(output)}." ) return None return output[input_key] else: return output return None def set_output(self, value: Union[List, str]) -> None: if isinstance(value, list): self._output = value elif isinstance(value, str): self._output = [value] else: raise TypeError("Value should be either a list or a string.") self.is_finished = True def get_inputs(self) -> Optional[List[InputItem]]: return self.inputs def get_input_processor(self) -> Optional["Processor"]: if self.input_processors is None or len(self.input_processors) == 0: return None return self.input_processors[0] def get_input_processors(self) -> List["Processor"]: return self.input_processors def get_input_node_output_key(self) -> Optional[int]: if self.inputs is None or len(self.inputs) == 0: return None if self.inputs[0].get("inputNodeOutputKey") is None: return 0 return self.inputs[0].get("inputNodeOutputKey") def get_input_node_output_key_by_node_name( self, input_node_name: str ) -> Optional[int]: keys = [] for input in self.inputs: if input.get("inputNode") == input_node_name: keys.append(input.get("inputNodeOutputKey")) return keys def get_input_node_output_keys(self) -> Optional[List[int]]: if self.inputs is None or len(self.inputs) == 0: return None return [input.get("inputNodeOutputKey") for input in self.inputs] def get_input_names(self) -> Optional[List[str]]: if self.inputs is None or len(self.inputs) == 0: return None return [input.get("inputName") for input in self.inputs] def get_input_names_from_config(self) -> Optional[List[str]]: return self._config.get("config").get("inputNames") def get_input_by_name( self, name: str, default=None, accept_object=False ) -> Optional[InputItem]: input = self._config.get(name, default) input_processors = self.get_input_processors() input_output_keys = self.get_input_node_output_keys() input_names = self.get_input_names() if input_processors: for processor, input_name, key in zip( input_processors, input_names, input_output_keys ): if input_name == name: input_processor_output = processor.get_output(key) if ( isinstance(input_processor_output, dict) or isinstance(input_processor_output, list) and not accept_object ): input_processor_output = json.dumps(input_processor_output) return input_processor_output return input def add_input_processor(self, input_processor: "Processor") -> None: self.input_processors.append(input_processor) def set_storage_strategy(self, storage_strategy: "StorageStrategy") -> None: self.storage_strategy = storage_strategy def __str__(self) -> str: return f"Processor(name={self.name}, type={self.processor_type})" def get_context(self) -> Optional["ProcessorContext"]: return self._processor_context def get_storage(self) -> Optional["StorageStrategy"]: return self.storage_strategy def has_dynamic_behavior(self) -> bool: return self._has_dynamic_behavior class BasicProcessor(Processor): def __init__(self, config): super().__init__(config) def cancel(self): pass class ContextAwareProcessor(Processor): def __init__(self, config, context: ProcessorContext = None): super().__init__(config) self._processor_context = context ================================================ FILE: packages/backend/app/processors/context/processor_context.py ================================================ from abc import ABC, abstractmethod from typing import Optional from typing import List class ProcessorContext(ABC): @abstractmethod def get_context(self) -> "ProcessorContext": pass @abstractmethod def get_current_user_id(self) -> Optional[str]: pass @abstractmethod def get_session_id(self) -> Optional[str]: pass @abstractmethod def get_parameter_names(self) -> List[str]: """ List all the parameter names currently stored in the context. Returns: A list of parameter names. """ pass @abstractmethod def get_value(self, name) -> Optional[str]: """ Retrieve the value associated with the specified parameter name. Returns: The value of the parameter if found, otherwise None. """ pass ================================================ FILE: packages/backend/app/processors/context/processor_context_flask_request.py ================================================ from typing import List, Optional from ...flask.utils.constants import SESSION_USER_ID_KEY from .processor_context import ProcessorContext from copy import deepcopy class ProcessorContextFlaskRequest(ProcessorContext): parameter_prefix = "session_" def __init__(self, g_context=None, session_data=None, session_id=None): self.g_context = deepcopy(g_context) if g_context is not None else {} self.session_data = deepcopy(session_data) if session_data is not None else {} self.session_id = deepcopy(session_id) if session_id is not None else None def get_context(self) -> "ProcessorContext": """Retrieve the stored Flask global context.""" return self.g_context def get_current_user_id(self) -> str: """Retrieve the current user ID from the stored session data.""" return self.session_data.get(SESSION_USER_ID_KEY) def get_session_id(self) -> str: return self.session_id def get_parameter_names(self) -> List[str]: return [ key.replace(self.parameter_prefix, "") for key in dir(self.g_context) if not key.startswith("_") and key not in dir(type(self.g_context)) ] def get_value(self, name) -> Optional[str]: return self.g_context.get(self.parameter_prefix + name) ================================================ FILE: packages/backend/app/processors/exceptions.py ================================================ class LightException(Exception): def __init__( self, message: str, langvar_message: str = "LightException", langvar_values: dict = None, ): self.message = message self.langvar_message = langvar_message self.langvar_values = langvar_values super().__init__(f"{message}") ================================================ FILE: packages/backend/app/processors/factory/processor_factory.py ================================================ from abc import ABC, abstractmethod from ...storage.storage_strategy import StorageStrategy from ..context.processor_context import ProcessorContext class ProcessorFactory(ABC): @abstractmethod def create_processor( self, config, context: ProcessorContext = None, storage_strategy: StorageStrategy = None, ): pass @abstractmethod def load_processors(self): pass ================================================ FILE: packages/backend/app/processors/factory/processor_factory_iter_modules.py ================================================ from enum import Enum import importlib import logging import pkgutil import inspect from ..components.processor import Processor from .processor_factory import ProcessorFactory from injector import singleton @singleton class ProcessorFactoryIterModules(ProcessorFactory): def __init__(self): self._processors = {} def register_processor(self, processor_type, processor_class): self._processors[processor_type] = processor_class def create_processor(self, config, context_data=None, storage_strategy=None): processor_type = config["processorType"] processor_class = self._processors.get(processor_type) if not processor_class: raise ValueError(f"Processor type '{processor_type}' not supported") params = inspect.signature(processor_class.__init__).parameters context_param = params.get("context") processor = None if context_param is not None: processor = processor_class(config=config, context=context_data) else: processor = processor_class(config=config) processor.set_storage_strategy(storage_strategy) return processor def load_processors(self): self._load_recursive("app.processors.components") def _load_recursive(self, package_name): package = importlib.import_module(package_name) prefix = package.__name__ + "." for importer, module_name, is_pkg in pkgutil.iter_modules( package.__path__, prefix ): if is_pkg: self._load_recursive(module_name) else: module = __import__(module_name, fromlist="dummy") for attribute_name in dir(module): attribute = getattr(module, attribute_name) if isinstance(attribute, type) and issubclass(attribute, Processor): if attribute.processor_type is not None: processor_type_key = ( attribute.processor_type.value if isinstance(attribute.processor_type, Enum) else attribute.processor_type ) self.register_processor(processor_type_key, attribute) ================================================ FILE: packages/backend/app/processors/launcher/abstract_topological_processor_launcher.py ================================================ from abc import abstractmethod import json import logging from typing import List from injector import inject from .processor_launcher import ProcessorLauncher from .event_type import EventType from .processor_launcher_event import ProcessorLauncherEvent from ..context.processor_context import ProcessorContext from ..observer.observer import Observer from ...storage.storage_strategy import StorageStrategy from ..factory.processor_factory import ProcessorFactory class AbstractTopologicalProcessorLauncher(ProcessorLauncher): """ Basic Processor Launcher emiting event through flask_socketio websockets A class that launches processors based on configuration data. """ processor_factory: ProcessorFactory storage_strategy: StorageStrategy observers: List[Observer] context: ProcessorContext @inject def __init__( self, processor_factory: ProcessorFactory, storage_strategy: StorageStrategy, observers: List[Observer] = None, ) -> None: self.processor_factory = processor_factory self.storage_strategy = storage_strategy self.processor_factory.load_processors() self.observers = observers or [] self.context = None def set_context(self, context: ProcessorContext): self.context = context def add_observer(self, observer): self.observers.append(observer) def _load_config_data(self, fileName): with open(fileName, "r") as file: config_data = json.load(file) return config_data def _link_processors(self, processors): for processor in processors.values(): if hasattr(processor, "inputs") and processor.inputs is not None: for input in processor.inputs: input_processor = processors.get(input.get("inputNode")) if not input_processor: logging.error( f"Link_processors - processor name : '{processor.name}' - input_processor : '{input.get('inputNode')}'" ) raise ValueError( f"Input processor '{input.get('inputNode')}' not found" ) processor.add_input_processor(input_processor) def load_processors(self, config_data): processors = { config["name"]: self.processor_factory.create_processor( config, self.context, self.storage_strategy ) for config in config_data } self._link_processors(processors) return processors def get_node_by_name(self, config_data, node_name): """ Retrieves a node by its name from the available nodes. Parameters: config_data (list): A list of dictionaries containing the configuration data for each processor. node_name (str): The name of the node to find. Returns: The node with the given name if found, otherwise None. """ for node in config_data: if node.get("name") == node_name: return node return None def notify_error(self, processor, e): error_event_data = ProcessorLauncherEvent( instance_name=processor.name, user_id=self.context.get_current_user_id(), processor=processor, error=e, session_id=self.context.get_session_id(), processor_type=processor.processor_type, ) self.notify_observers(EventType.ERROR.value, error_event_data) def notify_streaming(self, processor, output, isDone=False, duration=0): streaming_event_data = ProcessorLauncherEvent( instance_name=processor.name, user_id=self.context.get_current_user_id(), output=output, processor=processor, isDone=isDone, processor_type=processor.processor_type, session_id=self.context.get_session_id(), duration=duration, ) self.notify_observers(EventType.STREAMING.value, streaming_event_data) def notify_progress(self, processor, output, isDone=False, duration=0): progress_event_data = ProcessorLauncherEvent( instance_name=processor.name, user_id=self.context.get_current_user_id(), output=output, processor=processor, isDone=isDone, processor_type=processor.processor_type, session_id=self.context.get_session_id(), duration=duration, ) self.notify_observers(EventType.PROGRESS.value, progress_event_data) def notify_current_node_running(self, processor): current_node_running_event_data = ProcessorLauncherEvent( instance_name=processor.name, user_id=self.context.get_current_user_id(), processor=processor, session_id=self.context.get_session_id(), processor_type=processor.processor_type, ) self.notify_observers( EventType.CURRENT_NODE_RUNNING.value, current_node_running_event_data ) def load_required_processors(self, config_data, node_name): """ Loads the necessary processors based on the given configuration data and node name. Parameters: config_data (list): A list of dictionaries containing the configuration data for each processor. node_name (str): The name of the node being processed. Returns: dict: A dictionary mapping processor names to their respective instances. The function operates as follows: - Iterates over each configuration in config_data. - Creates a new processor instance based on the configuration. - If outputData is not None and differs from node_name, the processor's output is set accordingly. - Stores each processor instance in a dictionary with its name as the key. """ processors = {} node = self.get_node_by_name(config_data, node_name) if node and not node.get("inputs"): processor = self.processor_factory.create_processor( node, self.context, self.storage_strategy ) processors[node["name"]] = processor logging.debug(f"Created single processor for node - {node_name}") else: related_config_data = self.get_related_config_data( config_data, node_name, [] ) related_config_data.reverse() for config in related_config_data: config_output = config.get("outputData", None) if config_output is None or config["name"] == node_name: logging.debug(f"Empty or current node - {config['name']}") processor = self.processor_factory.create_processor( config, self.context, self.storage_strategy ) processors[config["name"]] = processor else: logging.debug(f"Non empty node - {config['name']}") processor = self.processor_factory.create_processor( config, self.context, self.storage_strategy ) processor.set_output(config_output) processors[config["name"]] = processor return processors def get_related_config_data(self, config_data, node_name, visited): if node_name in visited: return [] visited.append(node_name) current_config = next( (config for config in config_data if config["name"] == node_name), None ) if not current_config: return [] related_configs = [current_config] for input in current_config.get("inputs", []): related_configs.extend( self.get_related_config_data( config_data, input.get("inputNode"), visited ) ) return related_configs def load_processors_for_node(self, config_data, node_name): processors = self.load_required_processors(config_data, node_name) self._link_processors(processors) return processors @abstractmethod def launch_processors(self, processors): pass @abstractmethod def launch_processors_for_node(self, processors, node_name=None): pass def notify_observers(self, event, data): for observer in self.observers: observer.notify(event, data) ================================================ FILE: packages/backend/app/processors/launcher/async_processor_launcher.py ================================================ import gc import threading import time import eventlet from eventlet.semaphore import Semaphore import logging import traceback from typing import Dict, List from enum import Enum from .processor_event import ProcessorEvent from .event_type import EventType from ..observer.observer import Observer from ..components.processor import Processor from .abstract_topological_processor_launcher import ( AbstractTopologicalProcessorLauncher, ) class AsyncProcessorLauncher(AbstractTopologicalProcessorLauncher, Observer): """ AsyncProcessorLauncher extends the functionality of the Basic Processor Launcher. The main enhancement in this class is the implementation of the 'launch_processors' method, which leverages eventlet greenthreads. This allows for asynchronous execution of processors, enabling efficient handling of I/O-bound tasks and improving the overall performance of processor execution. """ GREENTHREAD_POOL_SIZE = 7 class NodeState(Enum): PENDING = 1 RUNNING = 2 COMPLETED = 3 ERROR = 4 class Node: def __init__(self, id: str, parent_ids: List[str], processor: Processor): self.id = id self.parent_ids = parent_ids self.state = AsyncProcessorLauncher.NodeState.PENDING self.output = None self.processor = processor self.lock = Semaphore(1) def run(self): with self.lock: if self.state != AsyncProcessorLauncher.NodeState.PENDING: logging.warning( f"Node {self.id} is already being processed or completed." ) return self.output self.state = AsyncProcessorLauncher.NodeState.RUNNING try: self.output = self.processor.process_and_update() except Exception as e: self.state = AsyncProcessorLauncher.NodeState.ERROR raise e self.state = AsyncProcessorLauncher.NodeState.COMPLETED return self.output def get_processor(self): return self.processor def get_input_processor_names(self, processor: Processor): return [ input_processor.name for input_processor in processor.get_input_processors() ] def convert_processors_to_node_dict(self, processors: List[Processor]): nodes = {} for processor in processors.values(): nodes[processor.name] = self.Node( processor.name, self.get_input_processor_names(processor), processor ) return nodes def launch_processors(self, processors: List[Processor]): for processor in processors.values(): processor.add_observer(self) nodes = self.convert_processors_to_node_dict(processors) pool = eventlet.GreenPool(AsyncProcessorLauncher.GREENTHREAD_POOL_SIZE) logging.debug(nodes) initialized_nodes = set() while nodes: error_detected = any( node.state == AsyncProcessorLauncher.NodeState.ERROR for node in nodes.values() ) if error_detected: logging.debug("A node is in ERROR state. Halting processing.") break for id, node in nodes.items(): if ( node.state == AsyncProcessorLauncher.NodeState.PENDING and self.can_run(node, nodes) and id not in initialized_nodes ): logging.debug(f"Spawning green thread for node {id}.") initialized_nodes.add(id) pool.spawn(self.run_node, node) eventlet.sleep(0.5) nodes = self.remove_completed_nodes(nodes) logging.debug(f"Remaining nodes: {[node.id for node in nodes.values()]}") pool.waitall() def remove_completed_nodes(self, nodes: List[Node]): return { id: n for id, n in nodes.items() if n.state not in [AsyncProcessorLauncher.NodeState.COMPLETED] } def can_run(self, node: Node, nodes: List[Node]): # If parents aren't in the list, then the node can run return all(parent_id not in nodes for parent_id in node.parent_ids) def launch_processors_for_node(self, processors: List[Processor], node_name=None): for processor in processors.values(): if processor.get_output() is None or processor.name == node_name: processor.add_observer(self) self.run_processor(processor) if processor.name == node_name: break def run_processor(self, processor: "Processor"): try: self.notify_current_node_running(processor) start_time = time.time() output = processor.process_and_update() end_time = time.time() duration = end_time - start_time self.notify_progress(processor, output, duration=duration, isDone=True) except Exception as e: self.notify_error(processor, e) raise e def run_node(self, node: Node): try: processor = node.get_processor() self.notify_current_node_running(processor) start_time = time.time() output = node.run() end_time = time.time() duration = end_time - start_time self.notify_progress(node.get_processor(), output, duration=duration) except Exception as e: node.state = AsyncProcessorLauncher.NodeState.ERROR self.notify_error(node.get_processor(), e) traceback.print_exc() raise e def notify(self, event: EventType, data: ProcessorEvent): if event == EventType.STREAMING: self.notify_streaming(data.source, data.output) ================================================ FILE: packages/backend/app/processors/launcher/basic_processor_launcher.py ================================================ from .abstract_topological_processor_launcher import AbstractTopologicalProcessorLauncher class BasicProcessorLauncher(AbstractTopologicalProcessorLauncher): """ Basic Processor Launcher emiting event A class that launches processors based on configuration data. """ def launch_processors(self, processors): for processor in processors.values(): self.notify_current_node_running(processor) try : output = processor.process() self.notify_progress(processor, output) except Exception as e: self.notify_error(processor, e) raise e def launch_processors_for_node(self, processors, node_name=None): for processor in processors.values(): if processor.get_output() is None or processor.name == node_name: self.notify_current_node_running(processor) try : output = processor.process() self.notify_progress(processor, output) except Exception as e: self.notify_error(processor, e) raise e if processor.name == node_name: break ================================================ FILE: packages/backend/app/processors/launcher/event_type.py ================================================ from enum import Enum class EventType(Enum): PROGRESS = "progress" STREAMING = "streaming" CURRENT_NODE_RUNNING = "current_node_running" ERROR = "error" ================================================ FILE: packages/backend/app/processors/launcher/processor_event.py ================================================ from dataclasses import dataclass, field from typing import Any @dataclass class ProcessorEvent: source: Any = field(default=None) output: Any = field(default=None) error: str = field(default=None) ================================================ FILE: packages/backend/app/processors/launcher/processor_launcher.py ================================================ from abc import ABC, abstractmethod from ..context.processor_context import ProcessorContext class ProcessorLauncher(ABC): @abstractmethod def load_processors(self, config_data): pass @abstractmethod def load_processors_for_node(self, config_data, node_name): pass @abstractmethod def launch_processors(self, processor): pass @abstractmethod def launch_processors_for_node(self, processors, node_name): pass @abstractmethod def set_context(self, context: ProcessorContext): pass ================================================ FILE: packages/backend/app/processors/launcher/processor_launcher_event.py ================================================ from dataclasses import dataclass, field from typing import Any from ..components.processor import Processor @dataclass class ProcessorLauncherEvent: instance_name: str user_id: int = field(default=None) output: Any = field(default=None) processor_type: str = field(default=None) processor: Processor = field(default=None) isDone: bool = field(default=False) error: str = field(default=None) session_id: str = field(default=None) duration: float = field(default=0) ================================================ FILE: packages/backend/app/processors/observer/observer.py ================================================ from abc import ABC, abstractmethod class Observer(ABC): @abstractmethod def notify(self, event, data): pass ================================================ FILE: packages/backend/app/processors/observer/socketio_event_emitter.py ================================================ from ..launcher.event_type import EventType from ..launcher.processor_launcher_event import ProcessorLauncherEvent from .observer import Observer import logging from ...flask.socketio_init import socketio class SocketIOEventEmitter(Observer): """ A SocketIO event emitter that emits events to clients connected via WebSocket. This class implements the Observer pattern and is designed to emit events to specific client sessions in a Flask-SocketIO application. It can be safely executed within greenthreads, making it suitable for use in environments where asynchronous operations and real-time communication are required. Attributes: None Methods: notify(event, data): Emits the specified event to the client associated with the session ID in `data`. Handles exceptions gracefully and logs emission details. """ def notify(self, event: EventType, data: ProcessorLauncherEvent): if event == EventType.STREAMING.value: event = EventType.PROGRESS.value json_event = {} json_event["instanceName"] = data.instance_name if data.output is not None: json_event["output"] = data.output if data.isDone is not None: json_event["isDone"] = data.isDone if data.error is not None: json_event["error"] = str(data.error) try: socketio.emit(event, json_event, to=data.session_id) logging.debug( f"Successfully emitted event {event} with data {json_event} to {data.session_id}" ) except Exception as e: logging.error(f"Error emitting event {event}: {e}") ================================================ FILE: packages/backend/app/processors/utils/retry_mixin.py ================================================ import time import logging class RetryMixin: def run_with_retry(self, func, *args, **kwargs): """ Executes `func` with retries as defined in the processor configuration. Expected configuration keys: - max_retries: number of extra attempts (default 0 means no retry) - retry_delay: delay (in seconds) between attempts (default 0) """ retries = getattr(self, "max_retries", 0) delay = getattr(self, "retry_delay", 0) for attempt in range(retries + 1): try: return func(*args, **kwargs) except Exception as e: logging.warning( f"Attempt {attempt+1}/{retries+1} for {func.__name__} failed" ) if attempt == retries: raise if delay: time.sleep(delay) ================================================ FILE: packages/backend/app/root_injector.py ================================================ from typing import List from injector import Injector, Binder, Module from tests.utils.processor_factory_mock import ProcessorFactoryMock from app.processors.launcher.async_processor_launcher import AsyncProcessorLauncher from app.processors.observer.socketio_event_emitter import SocketIOEventEmitter from app.processors.observer.observer import Observer from app.storage.local_storage_strategy import LocalStorageStrategy from app.storage.s3_storage_strategy import S3StorageStrategy from app.storage.storage_strategy import StorageStrategy from app.env_config import is_mock_env, is_s3_enabled from app.processors.factory.processor_factory import ProcessorFactory from app.processors.factory.processor_factory_iter_modules import ( ProcessorFactoryIterModules, ) from app.processors.launcher.processor_launcher import ProcessorLauncher import logging class ProcessorFactoryModule(Module): def configure(self, binder: Binder): if is_mock_env(): fake_factory = ProcessorFactoryMock(with_delay=True) binder.bind(ProcessorFactory, to=fake_factory) else: binder.bind(ProcessorFactory, to=ProcessorFactoryIterModules) class StorageModule(Module): def configure(self, binder: Binder): if is_s3_enabled(): logging.info("Using S3 storage strategy") binder.bind(StorageStrategy, to=S3StorageStrategy) else: logging.info("Using local storage strategy") binder.bind(StorageStrategy, to=LocalStorageStrategy) class ProcessorLauncherModule(Module): def configure(self, binder: Binder): binder.bind(ProcessorLauncher, to=AsyncProcessorLauncher) observer_list = [SocketIOEventEmitter()] binder.multibind(List[Observer], to=observer_list) def create_application_injector() -> Injector: injector = Injector( [ ProcessorFactoryModule(), StorageModule(), ProcessorLauncherModule(), ], auto_bind=True, ) return injector _current_injector: Injector = create_application_injector() def get_root_injector() -> Injector: return _current_injector def refresh_root_injector() -> None: global _current_injector _current_injector = create_application_injector() ================================================ FILE: packages/backend/app/storage/local_storage_strategy.py ================================================ from typing import Any from ..storage.storage_strategy import StorageStrategy from werkzeug.utils import secure_filename import os from app.env_config import ( get_local_storage_folder_path, ) from injector import singleton @singleton class LocalStorageStrategy(StorageStrategy): """Local storage strategy. To be used only when you're running the app on your own machine. Every generated image is saved in a local directory.""" LOCAL_DIR = get_local_storage_folder_path() def save(self, filename: str, data: Any) -> str: if not os.path.exists(self.LOCAL_DIR): os.makedirs(self.LOCAL_DIR) secure_name = secure_filename(filename) filepath = os.path.join(self.LOCAL_DIR, secure_name) with open(filepath, "wb") as f: f.write(data) return self.get_url(secure_name) def get_url(self, filename: str) -> str: port = os.getenv("PORT") return f"http://localhost:{port}/image/{filename}" def get_file(self, filename: str) -> bytes: pass ================================================ FILE: packages/backend/app/storage/s3_storage_strategy.py ================================================ import logging from typing import Any import uuid from ..storage.storage_strategy import CloudStorageStrategy import boto3 from botocore.config import Config import os from datetime import timedelta from injector import singleton import mimetypes import requests @singleton class S3StorageStrategy(CloudStorageStrategy): """S3 storage strategy. For the cloud version, every generated image is saved in an S3 bucket for 12H.""" EXPIRATION = timedelta(hours=24) UPLOAD_EXPIRATION = timedelta(minutes=10) MAX_UPLOAD_SIZE_BYTES = int(os.getenv("MAX_UPLOAD_SIZE_MB", "300")) * 1024 * 1024 MAX_POOL_CONNECTIONS = int(os.getenv("MAX_POOL_CONNECTIONS", "100")) def __init__(self): self.BUCKET_NAME = os.getenv("S3_BUCKET_NAME") endpoint_url = os.getenv("S3_ENDPOINT_URL") if not endpoint_url: endpoint_url = None kwargs = { "aws_access_key_id": os.getenv("S3_AWS_ACCESS_KEY_ID"), "aws_secret_access_key": os.getenv("S3_AWS_SECRET_ACCESS_KEY"), "region_name": os.getenv("S3_AWS_REGION_NAME"), "config": Config(max_pool_connections=self.MAX_POOL_CONNECTIONS), } if endpoint_url is not None: kwargs["endpoint_url"] = endpoint_url self.s3_client = boto3.client( "s3", **kwargs, ) def save(self, filename: str, data: Any, bucket_name: str = None) -> str: if bucket_name is None: bucket_name = self.BUCKET_NAME self.s3_client.put_object(Bucket=bucket_name, Key=filename, Body=data) url = self.s3_client.generate_presigned_url( ClientMethod="get_object", Params={"Bucket": bucket_name, "Key": filename}, ExpiresIn=int(self.EXPIRATION.total_seconds()), ) return url def get_upload_link(self, filename=None) -> str: file_key = f"uploads/{uuid.uuid4()}" content_type = None if not mimetypes.guess_type("test.webp")[0]: mimetypes.add_type("image/webp", ".webp") if not mimetypes.guess_type("test.safetensors")[0]: mimetypes.add_type("application/octet-stream", ".safetensors") if filename: extension = filename.split(".")[-1] file_key += f".{extension}" mime_type, _ = mimetypes.guess_type(filename) content_type = mime_type try: upload_data = self.s3_client.generate_presigned_post( Bucket=self.BUCKET_NAME, Key=file_key, Fields=None, Conditions=[["content-length-range", 0, self.MAX_UPLOAD_SIZE_BYTES]], ExpiresIn=int(self.UPLOAD_EXPIRATION.total_seconds()), ) download_url = self.s3_client.generate_presigned_url( ClientMethod="get_object", Params={ "Bucket": self.BUCKET_NAME, "Key": file_key, "ResponseContentType": content_type, }, ExpiresIn=int(self.EXPIRATION.total_seconds()), ) except Exception as e: logging.error(e) raise Exception( "Error uploading file. " "Please check your S3 configuration. " "If you've not configured S3 please refer to docs.ai-flow.net/docs/file-upload" ) return upload_data, download_url def get_url(self, filename: str, bucket_name: str = None) -> str: """Get presigned URL based on filename (URI)""" if bucket_name is None: bucket_name = self.BUCKET_NAME try: url = self.s3_client.generate_presigned_url( ClientMethod="get_object", Params={ "Bucket": bucket_name, "Key": filename, }, ExpiresIn=int(self.EXPIRATION.total_seconds()), ) return url except Exception as e: logging.error(f"Error generating presigned URL for {filename}: {e}") raise Exception("Error generating presigned URL. ") def get_file(self, filename: str, bucket_name: str = None) -> bytes: """Get file based on filename (URI)""" if filename.startswith("s3://"): filename = filename[len("s3://") :] filename = filename[filename.index("/") + 1 :] if bucket_name is None: bucket_name = self.BUCKET_NAME try: response = self.s3_client.get_object(Bucket=bucket_name, Key=filename) return response["Body"].read() except Exception as e: logging.error(f"Error getting file {filename}: {e}") raise Exception("Error getting file. ") def upload_and_get_link(self, filename: str, bucket_name: str = None) -> str: """Upload file and get link based on filename (URI)""" if bucket_name is None: bucket_name = self.BUCKET_NAME upload_data, download_url = self.get_upload_link(filename) url = upload_data["url"] fields = upload_data["fields"] filepath = filename if not os.path.isfile(filepath): raise FileNotFoundError(f"File '{filename}' does not exist.") with open(filepath, "rb") as file: files = {"file": file} response = requests.post(url, data=fields, files=files) if response.status_code == 204: logging.info("File uploaded successfully.") return download_url else: logging.error(f"Failed to upload file: {response.text}") response.raise_for_status() ================================================ FILE: packages/backend/app/storage/storage_strategy.py ================================================ from abc import ABC, abstractmethod from typing import Any, Optional class StorageStrategy(ABC): """Storage strategy interface. We use this storage strategy to save and get the url of documents. This is especially useful for the image generated by the stable diffusion model.""" @abstractmethod def save(self, filename: str, data: Any) -> Optional[str]: pass @abstractmethod def get_url(self, filename: str) -> str: pass @abstractmethod def get_file(self, filename: str, *args) -> bytes: pass class CloudStorageStrategy(StorageStrategy): @abstractmethod def get_upload_link(self, filename: str) -> str: pass ================================================ FILE: packages/backend/app/tasks/green_pool_task_manager.py ================================================ import logging from queue import Queue import eventlet from eventlet.green import threading from .task_exception import TaskAlreadyRegisteredError from ..env_config import get_background_task_max_workers task_queues = {} task_processors = {} task_semaphores = {} pool = eventlet.GreenPool(size=get_background_task_max_workers()) def register_task_processor(task_name, processor_func, max_concurrent_tasks=2): if task_name in task_queues: raise TaskAlreadyRegisteredError(task_name=task_name) task_queue = Queue() task_queues[task_name] = task_queue task_processors[task_name] = processor_func task_semaphores[task_name] = threading.Semaphore(max_concurrent_tasks) logging.info( f"Registered green pool task processor '{task_name}' with max_concurrent_tasks={max_concurrent_tasks}" ) def process_task(task_name, task_data, task_result_queue): semaphore = task_semaphores.get(task_name) if semaphore is not None: with semaphore: if task_name in task_processors: processor_func = task_processors[task_name] result = processor_func(task_data) task_result_queue.put(result) else: raise ValueError(f"Nao task processor registered for {task_name}") else: raise ValueError(f"No semaphore registered for {task_name}") def add_task(task_name, task_data, result_queue): if task_name in task_queues: return pool.spawn(process_task, task_name, task_data, result_queue) else: raise ValueError(f"No task processor registered for {task_name}") ================================================ FILE: packages/backend/app/tasks/single_thread_tasks/browser/async_browser_task.py ================================================ import logging import re import asyncio import threading from ....utils.web_scrapping.async_browser_manager import ( AsyncBrowserManager, ) browser_task_queue = None event_loop = None async def accept_cookies(page, cookies_consent_label, timeout=5000): try: await page.wait_for_selector( f"button:has-text('{cookies_consent_label}')", timeout=timeout ) accept_button = page.locator( f"button:has-text('{cookies_consent_label}')" ).first if not accept_button: return await accept_button.click() await page.wait_for_timeout(2000) except Exception as e: logging.warning("Could not find or click the cookie accept button:", e) def strip_attributes(html): return re.sub(r"(<\w+)(\s+[^>]+)?(>)", r"\1\3", html) async def fetch_url_content( url, browser_manager, with_html_tags=False, with_html_attributes=False, selectors=None, selectors_to_remove=None, auto_consent_cookies=False, enable_ad_blocker=False, cookies_consent_label=None, ): page, context = await browser_manager.get_tab() try: await page.goto(url, timeout=30000, wait_until="domcontentloaded") except Exception as e: logging.error(f"Failed to load page: {str(e)}") return "" try: await page.wait_for_load_state("networkidle", timeout=10000) content_attempts = 0 max_attempts = 3 while True: content_attempts += 1 try: content = await page.content() break except Exception as e: if "Page.content" in str(e): if content_attempts >= max_attempts: logging.error( f"Failed to retrieve page {url} content after {content_attempts} attempts: {str(e)}" ) return "" await page.wait_for_load_state("load", timeout=5000) else: raise if selectors_to_remove: for selector in selectors_to_remove: elements = await page.query_selector_all(selector) for element in elements: await page.evaluate("(element) => element.remove()", element) content = "" if selectors and len(selectors) > 0: for selector in selectors: await page.wait_for_selector(selector, timeout=3000) elements = await page.query_selector_all(selector) for element in elements: content_piece = ( await element.inner_html() if with_html_tags else await element.inner_text() ) content += content_piece + "\n" else: content = ( await page.content() if with_html_tags else await page.inner_text("body") ) if with_html_tags and not with_html_attributes: content = strip_attributes(content) except Exception as e: logging.error(f"Error processing {url} page content: {str(e)}") content = "" finally: await browser_manager.release_tab(page, context) return content async def scrapping_task(task_data, browser_manager): selectors = task_data.get("selectors", []) selectors_to_remove = task_data.get("selectors_to_remove", []) with_html_tags = task_data.get("with_html_tags", False) with_html_attributes = task_data.get("with_html_attributes", False) url = task_data.get("url") cookies_consent_label = task_data.get("cookies_consent_label", None) auto_consent_cookies = task_data.get("auto_consent_cookies", False) enable_ad_blocker = task_data.get("enable_ad_blocker", False) content = await fetch_url_content( url, browser_manager, with_html_tags=with_html_tags, with_html_attributes=with_html_attributes, selectors=selectors, selectors_to_remove=selectors_to_remove, cookies_consent_label=cookies_consent_label, auto_consent_cookies=auto_consent_cookies, enable_ad_blocker=enable_ad_blocker, ) return content async def add_task(task_data, result_queue): await browser_task_queue.put((task_data, result_queue)) async def browser_task_worker(): global browser_task_queue browser_task_queue = asyncio.Queue() browser_manager = AsyncBrowserManager() await browser_manager.initialize_browser() logging.info("Starting browser task worker") while True: task_data, result_queue = await browser_task_queue.get() if task_data is None: # Exit signal logging.info("Exiting browser task worker") break try: result = await scrapping_task(task_data, browser_manager) result_queue.put(result) except Exception as e: logging.error(f"Error in browser task worker: {e}") import traceback traceback.print_exc() event_loop = None def start_event_loop(): global event_loop event_loop = asyncio.new_event_loop() asyncio.set_event_loop(event_loop) event_loop.run_until_complete(browser_task_worker()) event_loop.run_forever() def stop_event_loop(): event_loop.run_until_complete(browser_manager.close_browser()) event_loop.stop() event_loop.close() def add_task_sync(task_data, result_queue): future = asyncio.run_coroutine_threadsafe( add_task(task_data, result_queue), event_loop ) return future event_loop_thread = threading.Thread(target=start_event_loop) event_loop_thread.start() ================================================ FILE: packages/backend/app/tasks/single_thread_tasks/browser/browser_task.py ================================================ import logging import queue import re import threading import time from ....utils.web_scrapping.browser_manager import BrowserManager browser_task_queue = queue.Queue() def accept_cookies(page, cookies_consent_label, timeout=5000): try: page.wait_for_selector( f"button:has-text('{cookies_consent_label}')", timeout=timeout ) accept_button = page.locator( f"button:has-text('{cookies_consent_label}')" ).first accept_button.click() page.wait_for_timeout(2000) except Exception as e: logging.warning("Could not find or click the cookie accept button:", e) def strip_attributes(html): return re.sub(r"(<\w+)(\s+[^>]+)?(>)", r"\1\3", html) def fetch_url_content( url, browser_manager, with_html_tags=False, with_html_attributes=False, selectors=None, selectors_to_remove=None, cookies_consent_label=None, ): page, context = browser_manager.get_tab() try: page.goto(url, timeout=30000) except Exception as e: logging.error(f"Failed to load page: {str(e)}") return "" try: if cookies_consent_label: accept_cookies(page, cookies_consent_label) if selectors_to_remove: for selector in selectors_to_remove: elements = page.query_selector_all(selector) for element in elements: page.evaluate("(element) => element.remove()", element) content = "" if selectors and len(selectors) > 0: for selector in selectors: elements = page.query_selector_all(selector) for element in elements: content_piece = ( element.inner_html() if with_html_tags else element.inner_text() ) content += content_piece + "\n" else: content = page.content() if with_html_tags else page.inner_text("body") if with_html_tags and not with_html_attributes: content = strip_attributes(content) except Exception as e: logging.error(f"Error processing page content: {str(e)}") content = "" finally: browser_manager.release_tab(page, context) return content def scrapping_task(task_data, browser_manager): selectors = task_data.get("selectors", []) selectors_to_remove = task_data.get("selectors_to_remove", []) with_html_tags = task_data.get("with_html_tags", False) with_html_attributes = task_data.get("with_html_attributes", False) url = task_data.get("url") cookies_consent_label = task_data.get("cookies_consent_label", None) content = fetch_url_content( url, browser_manager, with_html_tags=with_html_tags, with_html_attributes=with_html_attributes, selectors=selectors, selectors_to_remove=selectors_to_remove, cookies_consent_label=cookies_consent_label, ) return content def add_task_sync(task_data, result_queue): browser_task_queue.put((task_data, result_queue)) def browser_thread_func(task_queue): logging.info("Starting browser thread") browser_manager = BrowserManager() browser_manager.initialize_browser() while True: try: task_data, result_queue = task_queue.get() if task_data is None: # Exit signal logging.info("Exiting browser thread") break result = scrapping_task(task_data, browser_manager) result_queue.put(result) except Exception as e: logging.error(f"Error in browser thread: {e}") finally: time.sleep(0.1) def stop_browser_thread(): browser_task_queue.put((None, None)) browser_thread.join() browser_thread = threading.Thread( target=browser_thread_func, args=(browser_task_queue,) ) browser_thread.start() ================================================ FILE: packages/backend/app/tasks/task_exception.py ================================================ class TaskAlreadyRegisteredError(Exception): """Exception raised when attempting to register a task that is already registered.""" def __init__(self, task_name): self.task_name = task_name super().__init__(f"Task '{task_name}' is already registered.") ================================================ FILE: packages/backend/app/tasks/task_manager.py ================================================ import logging from queue import Queue from concurrent.futures import ThreadPoolExecutor import threading from .task_exception import TaskAlreadyRegisteredError from ..env_config import get_background_task_max_workers task_queues = {} task_processors = {} task_semaphores = {} executor = ThreadPoolExecutor(max_workers=get_background_task_max_workers()) def register_task_processor(task_name, processor_func, max_concurrent_tasks=2): if task_name in task_queues: raise TaskAlreadyRegisteredError(task_name=task_name) task_queue = Queue() task_queues[task_name] = task_queue task_processors[task_name] = processor_func task_semaphores[task_name] = threading.Semaphore(max_concurrent_tasks) logging.info( f"Registered task processor '{task_name}' with max_concurrent_tasks={max_concurrent_tasks}" ) def process_task(task_name, task_data, task_result_queue): semaphore = task_semaphores.get(task_name) if semaphore is not None: with semaphore: if task_name in task_processors: processor_func = task_processors[task_name] result = processor_func(task_data) task_result_queue.put(result) else: raise ValueError(f"No task processor registered for {task_name}") else: raise ValueError(f"No semaphore registered for {task_name}") def add_task(task_name, task_data, result_queue): if task_name in task_queues: executor.submit(process_task, task_name, task_data, result_queue) else: raise ValueError(f"No task processor registered for {task_name}") ================================================ FILE: packages/backend/app/tasks/task_utils.py ================================================ from queue import Empty import time import eventlet def wait_for_result(queue, timeout=120, initial_sleep=0.1, max_sleep=5.0): start_time = time.time() sleep_duration = initial_sleep while True: try: result = queue.get_nowait() return result except Empty: if time.time() - start_time >= timeout: raise TimeoutError("Operation timed out after the specified timeout") eventlet.sleep(sleep_duration) sleep_duration = min(sleep_duration * 1.5, max_sleep) ================================================ FILE: packages/backend/app/tasks/thread_pool_task_manager.py ================================================ from concurrent.futures import ThreadPoolExecutor import logging from queue import Queue import threading from .task_exception import TaskAlreadyRegisteredError from ..env_config import get_background_task_max_workers task_queues = {} task_processors = {} task_semaphores = {} executor = ThreadPoolExecutor(max_workers=get_background_task_max_workers()) def register_task_processor(task_name, processor_func, max_concurrent_tasks=2): if task_name in task_queues: raise TaskAlreadyRegisteredError(task_name=task_name) task_queue = Queue() task_queues[task_name] = task_queue task_processors[task_name] = processor_func task_semaphores[task_name] = threading.Semaphore(max_concurrent_tasks) logging.info( f"Registered thread pool task processor '{task_name}' with max_concurrent_tasks={max_concurrent_tasks}" ) def process_task(task_name, task_data, task_result_queue): semaphore = task_semaphores.get(task_name) if semaphore is not None: with semaphore: if task_name in task_processors: processor_func = task_processors[task_name] result = processor_func(task_data) task_result_queue.put(result) else: raise ValueError(f"Nao task processor registered for {task_name}") else: raise ValueError(f"No semaphore registered for {task_name}") def add_task(task_name, task_data, result_queue): if task_name in task_queues: return executor.submit(process_task, task_name, task_data, result_queue) else: raise ValueError(f"No task processor registered for {task_name}") ================================================ FILE: packages/backend/app/utils/node_extension_utils.py ================================================ import importlib import logging import os import pkgutil from cachetools import TTLCache, cached from ..processors.components.extension.extension_processor import ( DynamicExtensionProcessor, ExtensionProcessor, ) very_long_ttl_cache = 120000 raw_blacklist = os.getenv("EXTENSIONS_BLACKLIST", "").strip() EXTENSIONS_BLACKLIST = raw_blacklist.split(",") if raw_blacklist else [] raw_whitelist = os.getenv("EXTENSIONS_WHITELIST", "").strip() EXTENSIONS_WHITELIST = raw_whitelist.split(",") if raw_whitelist else [] def _load_dynamic_extension(processor_type, data): package = importlib.import_module("app.processors.components.extension") prefix = package.__name__ + "." for importer, module_name, is_pkg in pkgutil.iter_modules(package.__path__, prefix): module = importlib.import_module(module_name) for attribute_name in dir(module): attribute = getattr(module, attribute_name) if isinstance(attribute, type) and issubclass( attribute, DynamicExtensionProcessor ): if hasattr(attribute, "get_dynamic_node_config") and hasattr( attribute, "processor_type" ): if attribute.processor_type == processor_type: schema = attribute.get_dynamic_node_config(attribute, data) return schema return None def _load_all_extension_schemas(): schemas = [] package = importlib.import_module("app.processors.components.extension") prefix = package.__name__ + "." for importer, module_name, is_pkg in pkgutil.iter_modules(package.__path__, prefix): module = importlib.import_module(module_name) for attribute_name in dir(module): try: attribute = getattr(module, attribute_name) if isinstance(attribute, type) and issubclass( attribute, ExtensionProcessor ): if hasattr(attribute, "get_node_config"): schema = attribute.get_node_config(attribute) if schema is not None: schemas.append(schema) except Exception as e: logging.warning( f"Error loading extension {module_name}.{attribute_name}" ) continue return schemas def filter_extensions(extensions): if len(EXTENSIONS_WHITELIST) > 0: extensions = [e for e in extensions if e.processorType in EXTENSIONS_WHITELIST] if len(EXTENSIONS_BLACKLIST) > 0: extensions = [ e for e in extensions if e.processorType not in EXTENSIONS_BLACKLIST ] return extensions def get_extensions(): schemas = _load_all_extension_schemas() schemas = filter_extensions(schemas) schemas_dict = [] for schema in schemas: if schema is not None: schemas_dict.append(schema.dict()) return schemas_dict def get_dynamic_extension_config(processor_type, data): schema = _load_dynamic_extension(processor_type, data) return schema ================================================ FILE: packages/backend/app/utils/openapi_client.py ================================================ import logging from typing import Optional import requests import eventlet class Client: def __init__( self, api_token: Optional[str] = None, base_url: Optional[str] = None, timeout_ms: int = None, **kwargs, ) -> None: super().__init__() self._api_token = api_token self._base_url = base_url self._timeout_ms = timeout_ms self._client_kwargs = kwargs def post( self, path: str, data: Optional[dict] = None, files: Optional[dict] = {"none": (None, "")}, content_type: str = None, accept: str = "application/json", **kwargs, ) -> dict: headers = { "Authorization": f"Bearer {self._api_token}", "Accept": accept, } if files: response = requests.post( f"{self._base_url}{path}", headers=headers, files=files, data=data, timeout=self._timeout_ms, **self._client_kwargs, **kwargs, ) else: logging.info("JSON BOI") response = requests.post( f"{self._base_url}{path}", headers=headers, json=data, timeout=self._timeout_ms, **self._client_kwargs, **kwargs, ) if response.status_code == 200: if accept == "application/json": return response.json() elif accept == "image/*": return response.content else: return response.content else: raise Exception(response.text) def get( self, path: str, content_type: str = None, accept: str = "application/json", **kwargs, ) -> dict: response = requests.get( f"{self._base_url}{path}", headers={ "Content-Type": content_type, "Authorization": f"Bearer {self._api_token}", "Accept": accept, }, timeout=self._timeout_ms, **self._client_kwargs, **kwargs, ) if response.status_code == 200: if accept == "application/json": return response.json() elif accept == "image/*": return response.content else: return response.content else: print(response.status_code) if response.json()["status"] == "in-progress": return response.json() raise Exception(str(response.json())) def pooling( self, path: str, content_type: str = None, accept: str = "application/json", **kwargs, ) -> dict: pooling_response = self.get(path=path, accept=accept) while ( isinstance(pooling_response, dict) and pooling_response.get("status") == "in-progress" ): print("Pooling...") eventlet.sleep(0.5) pooling_response = self.get(path=path, accept=accept) return pooling_response # if __name__ == "__main__": # api_reader = OpenAPIReader("../../resources/openapi/stabilityai.json") # print("API Key Name:", api_reader.get_api_key_name()) # specific_path = "/v2beta/stable-image/generate/core" # Remplacez par un chemin valide de votre fichier OpenAPI # params = api_reader.get_request_schema_for_path(specific_path, "POST") # print(params) # requestBody = { # "prompt": "A cute baby sea otter", # "aspect_ratio": "16:9", # } # serverUrl = api_reader.get_servers()[0] # print(serverUrl) # client = Client( # api_token="sk-rQKpzMtDplCq8NFjnPxKOKkFzjmdEsticRFMUWUk11uPrflL", # base_url=serverUrl, # ) # response = client.post(specific_path, requestBody, accept="image/*") # print(response) ================================================ FILE: packages/backend/app/utils/openapi_converter.py ================================================ import json from ..processors.components.model import Option from ..processors.components.node_config_builder import ( FieldBuilder, NodeConfigBuilder, NodeConfigVariantBuilder, ) class OpenAPIConverter: def __init__(self): pass def convert_enum_to_options(self, enum, defaultValue): options = [] for value in enum: options.append( Option(default=(value == defaultValue), value=value, label=value) ) return options def convert_properties_to_fields(self, schema): fields = [] properties = schema.get("properties", {}) for key, value in properties.items(): field_builder = FieldBuilder() field_builder.set_name(key) field_builder.set_label(key) field_builder.set_description(value.get("description")) field_type = value.get("type") if field_type == "string": if "enum" in value: field_builder.set_type("select") field_builder.set_options( self.convert_enum_to_options( value["enum"], value.get("default") ) ) else: field_builder.set_type("textfield") field_builder.set_has_handle(True) elif field_type == "number": if "minimum" or "maximum" in value: if value.get("maximum") > 1000: field_builder.set_type("numericfield") else: field_builder.set_type("slider") field_builder.set_min(value.get("minimum")) field_builder.set_max(value.get("maximum")) else: field_builder.set_type("numericfield") field_builder.set_has_handle(True) else: field_builder.set_type("textfield") if "example" in value: if "format" in value and value["format"] == "binary": field_builder.set_placeholder("Resource URL") field_builder.set_is_binary(True) else: field_builder.set_placeholder(value["example"]) if "default" in value: field_builder.set_default_value(value["default"]) required_fields = schema.get("required", []) if key in required_fields: field_builder.set_required(True) fields.append(field_builder.build()) return fields def convert_schema_to_node_config(self, schema): schema = schema.get("schema") if schema.get("oneOf") and schema.get("discriminator"): builder = NodeConfigVariantBuilder() discriminatorName = schema.get("discriminator").get("propertyName") mapping = schema.get("discriminator", {}).get("mapping") builder.add_discriminator_field(discriminatorName) for key, value in mapping.items(): fields = self.convert_properties_to_fields(value) builder.add_sub_configuration( NodeConfigBuilder() .add_discriminator(discriminatorName, key) .set_fields(fields) .build() ) else: builder = NodeConfigBuilder() # print(schema) fields = self.convert_properties_to_fields(schema) builder.set_fields(fields) return builder ================================================ FILE: packages/backend/app/utils/openapi_reader.py ================================================ import json import hashlib from openapi_spec_validator.readers import read_from_filename class OpenAPIReader: HTTP_METHODS_LIST = ["get", "post", "put", "delete", "patch", "options", "head"] def __init__(self, file_path): spec_dict, base_uri = read_from_filename(file_path) self._api_data = spec_dict self._paths = self.get_all_paths() def get_api_key_name(self): security_schemes = self._api_data.get("components", {}).get( "securitySchemes", {} ) for key, value in security_schemes.items(): if value.get("type") == "apiKey": return key return None def get_servers(self): servers = self._api_data.get("servers", []) return [server["url"] for server in servers if "url" in server] def get_all_paths(self): paths = self._api_data.get("paths", {}) path_details = {} for path, operations in paths.items(): operations_info = {} for operation_type, operation_details in operations.items(): if operation_type in OpenAPIReader.HTTP_METHODS_LIST: operations_info[operation_type.upper()] = { "summary": operation_details.get( "summary", "No summary provided" ), "description": operation_details.get( "description", "No description provided" ), } path_details[path] = operations_info return path_details def get_all_paths_names(self): return list(self._paths.keys()) def get_params_for_path(self, path, method): path_details = self._api_data.get("paths", {}).get(path, {}) params = {} for path_method, details in path_details.items(): if method.upper() == path_method.upper(): params[method.upper()] = details.get("parameters", []) return json.dumps(params, indent=4) def get_path_accept(self, path, method): path_details = self._api_data.get("paths", {}).get(path, {}) parameters = path_details[method].get("parameters", {}) for param in parameters: if param["name"] == "accept": return param["schema"]["default"] return None def get_response_content_type(self, path, method): path_details = self._api_data.get("paths", {}).get(path, {}) responses = path_details[method].get("responses", {}) response_200 = responses.get("200", {}) content_types = response_200.get("content", {}) return list(content_types.keys()) def get_request_schema_for_path(self, path, method, content_type=None): path_details = self._api_data.get("paths", {}).get(path, {}) if method.lower() in path_details: request_body = path_details[method.lower()].get("requestBody", {}) content_types = request_body.get("content", {}) resolved_request_body = {} for request_content_type, content_details in content_types.items(): schema = content_details.get("schema", {}) resolved_schema = self.resolve_schema( schema ) # Recursively resolve schema including nested $ref resolved_request_body[request_content_type] = { "schema": resolved_schema } if content_type and content_type in resolved_request_body: return resolved_request_body[content_type] elif resolved_request_body: first_content_type = next(iter(resolved_request_body)) return resolved_request_body[first_content_type] return "{}" def merge_schemas(base_schema, additions): if "required" in additions: if "required" in base_schema: base_schema["required"].extend(additions["required"]) else: base_schema["required"] = additions["required"] return base_schema def resolve_schema(self, schema): """Recursively resolve schema references including nested references.""" if "$ref" in schema: return self.resolve_ref(schema["$ref"]) elif "schema" in schema: return self.resolve_schema(schema["schema"]) elif "allOf" in schema: allOf = schema["allOf"] resolved_parts = [] for item in allOf: resolved_parts.append(self.resolve_schema(item)) resolved_path = resolved_parts[0] for part in resolved_parts[1:]: resolved_path = OpenAPIReader.merge_schemas(resolved_path, part) return resolved_path elif "oneOf" in schema: oneOf = schema["oneOf"] resolved_parts = [] for item in oneOf: resolved_parts.append(self.resolve_schema(item)) schema["oneOf"] = resolved_parts if "discriminator" in schema: discriminator = schema["discriminator"] discriminator_mapping = discriminator["mapping"] discriminator_resolved = {} for key, value in discriminator_mapping.items(): if type(value) != str: discriminator_resolved[key] = value else: discriminator_resolved[key] = self.resolve_ref(value) discriminator["mapping"] = discriminator_resolved return schema elif schema.get("type") == "object" and "properties" in schema: # If the schema is of type object and has properties, recursively resolve each property resolved_properties = {} for prop, prop_schema in schema["properties"].items(): resolved_properties[prop] = self.resolve_schema(prop_schema) # Return the schema with resolved properties, preserving other aspects of the schema such as 'required' return { "type": "object", "properties": resolved_properties, "required": schema.get("required", []), } elif schema.get("type") == "array" and "items" in schema: resolved_items = self.resolve_schema(schema["items"]) return {"type": "array", "items": resolved_items} return schema def resolve_ref(self, ref): ref_parts = ref.split("/") if ref_parts[0] == "#": # Assumes the first part is '#' component_part = self._api_data for part in ref_parts[1:]: component_part = component_part.get(part, {}) return component_part return {} def get_response_schema_for_path(self, path, method, content_type=None): path_details = self._api_data.get("paths", {}).get(path, {}) if method.lower() in path_details: responses = path_details[method.lower()].get("responses", {}) response_200 = responses.get("200", {}) if "content" in response_200: content = response_200["content"] if content_type: # If a specific content type is requested, return that schema if content_type in content: return self.resolve_schema(content[content_type]) else: # Otherwise, return the schema for the first available content type first_content_type = list(content.keys())[0] return self.resolve_schema(content[first_content_type]) return "{}" # Usage de la classe if __name__ == "__main__": def resolve_references(schema, root): if isinstance(schema, dict): if "$ref" in schema: ref_path = schema["$ref"] ref_parts = ref_path.strip("#/").split("/") ref_schema = root for part in ref_parts: ref_schema = ref_schema[part] return resolve_references(ref_schema, root) else: return {k: resolve_references(v, root) for k, v in schema.items()} elif isinstance(schema, list): return [resolve_references(item, root) for item in schema] else: return schema spec_dict, base_uri = read_from_filename("../../resources/openapi/stabilityai.json") reader = OpenAPIReader("../../resources/openapi/stabilityai.json") path_img_to_video = reader.get_request_schema_for_path( "/v2beta/image-to-video", "post" ) resolved_path = reader.resolve_schema(path_img_to_video) print(json.dumps(resolved_path, indent=4)) ================================================ FILE: packages/backend/app/utils/processor_utils.py ================================================ from datetime import datetime import os import tempfile from urllib.parse import urlparse import requests def create_empty_tmp_file(prefix="tmp"): temp_dir = tempfile.TemporaryDirectory() timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S%f") temp_file = os.path.join(temp_dir.name, f"{prefix}-{timestamp_str}") return temp_file, temp_dir def create_temp_file_with_str_content(content): temp_file, temp_dir = create_empty_tmp_file() with open(temp_file, "w") as f: f.write(content) return temp_file, temp_dir def create_temp_file_with_bytes_content(content): temp_file, temp_dir = create_empty_tmp_file() with open(temp_file, "wb") as f: f.write(content) return temp_file, temp_dir def get_file_size_from_url(url): response = requests.head(url) if response.status_code != 200: raise ValueError( f"Failed to reach the URL: returned status code {response.status_code}" ) content_length = response.headers.get("Content-Length") return content_length def get_file_size_from_url_in_mb(url): content_length = get_file_size_from_url(url) if content_length is None: raise ValueError("Failed to get file size from URL") return round(int(content_length) / (1024 * 1024), 2) def get_max_file_size_in_mb(): return int(os.getenv("MAX_TMP_FILE_SIZE_MB", 300)) def is_s3_file(url): bucket_name = os.getenv("S3_BUCKET_NAME") if not bucket_name: return False return url.startswith(f"s3://{bucket_name}") or url.startswith( f"https://{bucket_name}.s3.amazonaws.com" ) def is_accepted_url_file_size(url): size_mb = get_file_size_from_url_in_mb(url) if size_mb > get_max_file_size_in_mb(): return False return True def is_valid_url(url): result = urlparse(url) if not all([result.scheme, result.netloc]): return False return True def file_downloable_check(url): if is_s3_file(url): return if not is_accepted_url_file_size(url): raise ValueError( f"File size is too large. Max file size is {get_max_file_size_in_mb()} MB" ) if not is_valid_url(url): raise ValueError(f"Invalid URL: {url}") def download_file_as_binary(url): try: file_downloable_check(url) except ValueError as e: raise ValueError(f"Can't download file: {e}") response = requests.get(url) response.raise_for_status() return response.content def stream_download_file_as_binary(url): try: file_downloable_check(url) except ValueError as e: raise ValueError(f"Can't download file: {e}") with requests.get(url, stream=True) as response: response.raise_for_status() chunks = [] for chunk in response.iter_content(chunk_size=8192): chunks.append(chunk) return b"".join(chunks) ================================================ FILE: packages/backend/app/utils/replicate_utils.py ================================================ import os import requests from ..env_config import get_replicate_api_key from cachetools import TTLCache, cached import logging short_ttl_cache = 600 long_ttl_cache = 12000 very_long_ttl_cache = 120000 REPLICATE_API_URL = "https://api.replicate.com" REPLICATE_MODEL_API_URL = f"{REPLICATE_API_URL}/v1/models" REPLICATE_COLLECTION_API_URL = f"{REPLICATE_API_URL}/v1/collections" @cached(TTLCache(maxsize=100, ttl=600)) def get_replicate_models(cursor: str = None): api_token = get_replicate_api_key() if not api_token: raise Exception("Replicate API token not found in environment variables") headers = {"Authorization": f"Token {api_token}"} url = REPLICATE_MODEL_API_URL if cursor: url += f"?cursor={cursor}" response = requests.get(url=url, headers=headers) if response.status_code != 200: raise Exception(f"Failed to fetch models: {response.status_code}") data = response.json() for model in data["results"]: if "latest_version" in model: del model["latest_version"] if "default_example" in model: del model["default_example"] return data @cached(TTLCache(maxsize=100, ttl=long_ttl_cache)) def get_replicate_collections(): api_token = get_replicate_api_key() if not api_token: raise Exception("Replicate API token not found in environment variables") headers = {"Authorization": f"Token {api_token}"} response = requests.get(REPLICATE_COLLECTION_API_URL, headers=headers) if response.status_code != 200: raise Exception(f"Failed to fetch collections: {response.status_code}") collections = response.json() return collections @cached(TTLCache(maxsize=100, ttl=long_ttl_cache)) def get_replicate_collection_models(collection_slug: str, cursor=None): api_token = get_replicate_api_key() if not api_token: raise Exception("Replicate API token not found in environment variables") headers = {"Authorization": f"Token {api_token}"} url = f"{REPLICATE_COLLECTION_API_URL}/{collection_slug}" if cursor: url += f"?cursor={cursor}" response = requests.get(url=url, headers=headers) if response.status_code != 200: raise Exception( f"Failed to fetch collections: {collection_slug} - {response.status_code}" ) data = response.json() for model in data["models"]: if "latest_version" in model: del model["latest_version"] if "default_example" in model: del model["default_example"] return data @cached(TTLCache(maxsize=100, ttl=very_long_ttl_cache)) def get_highlighted_models_info(): models_str = os.getenv("REPLICATE_MODELS_HIGHLIGHTED", None) models = [] if models_str: models = models_str.split(",") models = [model.strip() for model in models] else: return None models_info = [] for model in models: try: info = get_model_info(model) models_info.append(info) except Exception as e: logging.error(f"Failed to fetch model info for {model}: {e}") continue return models_info @cached(TTLCache(maxsize=100, ttl=long_ttl_cache)) def get_model_info(model_id: str): api_token = get_replicate_api_key() url = f"{REPLICATE_MODEL_API_URL}/{model_id}" headers = {"Authorization": f"Token {api_token}"} response = requests.get(url, headers=headers) if response.status_code != 200: raise Exception( f"Failed to fetch model info: {model_id} - {response.status_code}" ) model = response.json() if "latest_version" in model: del model["latest_version"] if "default_example" in model: del model["default_example"] return model @cached(TTLCache(maxsize=100, ttl=long_ttl_cache)) def get_model_openapi_schema(model_id: str): api_token = get_replicate_api_key() url = f"{REPLICATE_MODEL_API_URL}/{model_id}" headers = {"Authorization": f"Token {api_token}"} response = requests.get(url, headers=headers) if response.status_code != 200: raise Exception( f"Failed to fetch model schema: {model_id} - {response.status_code}" ) version = response.json().get("latest_version") schema = version.get("openapi_schema", None) if not schema: raise Exception( f"OpenAPI schema not found in the response - model_id : {model_id}" ) return { "schema": schema, "modelId": version["id"], } def get_input_schema_from_open_API_schema(openapi_schema): input_schema = openapi_schema["components"]["schemas"]["Input"] return input_schema def get_output_schema_from_open_API_schema(openapi_schema): output_schema = openapi_schema["components"]["schemas"]["Output"] return output_schema ================================================ FILE: packages/backend/app/utils/web_scrapping/async_browser_manager.py ================================================ import logging import asyncio from asyncio import Queue from queue import Empty import tempfile import time import zipfile from ...env_config import get_browser_tab_max_usage, get_browser_tab_pool_size from playwright.async_api import async_playwright class AsyncBrowserManager: def __init__(self): self.playwright = None self.browser = None self.pool_size = get_browser_tab_pool_size() self.max_usage = get_browser_tab_max_usage() self.lock = asyncio.Lock() self.tab_pool = Queue(maxsize=self.pool_size) self.tab_usage_count = {} async def initialize_browser(self): self.playwright = await async_playwright().start() await self.initialize_pool() logging.info("Browser initialized") def unzip_extension(zip_path, extract_to): with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(extract_to) async def launch_context(self): user_data_dir = tempfile.mkdtemp() args = [] args.append("--headless=new") context = await self.playwright.chromium.launch_persistent_context( user_data_dir, headless=False, args=args, viewport={"width": 1920, "height": 1080}, ) return context async def initialize_pool(self): for _ in range(self.pool_size): context = await self.launch_context() page = await context.new_page() await self.tab_pool.put((page, context)) self.tab_usage_count[page] = 0 async def check_extensions_loaded(self, take_extensions_screenshot=False): page, context = await self.get_tab() await page.goto("chrome://extensions/") # Wait for the extensions list to load await page.wait_for_selector("extensions-manager") # Extract the extensions displayed extensions = await page.evaluate( """() => { return new Promise((resolve, reject) => { try { chrome.management.getAll((extensions) => { resolve(extensions); }); } catch (error) { reject(error); } }); }""" ) extension_names = [ext["name"] for ext in extensions] logging.info(f"Extensions loaded in Chromium : {extension_names}") if take_extensions_screenshot: screenshot_path = "extensions_screenshot.png" await page.screenshot(path=screenshot_path) logging.info(f"Screenshot saved to {screenshot_path}") async def close_browser(self): if self.browser: await self.browser.close() if self.playwright: await self.playwright.stop() async def _recycle_tab(self, page, context): try: await context.close() # Close existing context to free memory except Exception as e: logging.error(f"Error closing context: {e}") context = await self.launch_context() page = await context.new_page() self.tab_usage_count[page] = 1 return page, context async def get_tab(self, timeout=10): start_time = time.time() while time.time() - start_time < timeout: try: async with self.lock: page, context = await self.tab_pool.get() self.tab_usage_count[page] += 1 if self.tab_usage_count[page] > self.max_usage: # Recycle tab if max usage is reached page, context = await self._recycle_tab(page, context) return page, context except Empty: await asyncio.sleep(0.1) # Yield control to other tasks raise Exception("No available tabs in the pool after waiting") async def release_tab(self, page, context): async with self.lock: try: await context.clear_cookies() await self.tab_pool.put((page, context)) except Exception as e: logging.error(f"Error releasing tab: {e}") # Recycle the tab if an error occurs during release page, context = await self._recycle_tab(page, context) await self.tab_pool.put((page, context)) ================================================ FILE: packages/backend/app/utils/web_scrapping/browser_manager.py ================================================ import logging import os from queue import Empty, Queue import time import threading from ...env_config import get_browser_tab_max_usage, get_browser_tab_pool_size from playwright.sync_api import sync_playwright class BrowserManager: def __init__(self, pool_size=None, max_usage=None): self.playwright = None self.browser = None self.pool_size = get_browser_tab_pool_size() if pool_size is None else pool_size self.max_usage = get_browser_tab_max_usage() if max_usage is None else max_usage self.lock = threading.Lock() self.tab_pool = Queue(maxsize=self.pool_size) self.tab_usage_count = {} def initialize_browser(self): self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=True) self.initialize_pool() logging.info("Browser initialized") def initialize_pool(self): for _ in range(self.pool_size): context = self.browser.new_context() page = context.new_page() self.tab_pool.put((page, context)) self.tab_usage_count[page] = 0 def close_browser(self): if self.browser: self.browser.close() if self.playwright: self.playwright.stop() def get_browser(self): if not self.browser: raise Exception("Browser not initialized") return self.browser def _recycle_tab(self, page, context): context.close() context = self.browser.new_context() page = context.new_page() self.tab_usage_count[page] = 1 return page, context def get_tab(self, timeout=10): start_time = time.time() while time.time() - start_time < timeout: try: with self.lock: page, context = self.tab_pool.get_nowait() self.tab_usage_count[page] += 1 if self.tab_usage_count[page] > self.max_usage: # Recycle tab if max usage is reached page, context = self._recycle_tab(page, context) return page, context except Empty: time.sleep(0.1) # Yield control to other threads raise Exception("No available tabs in the pool after waiting") def release_tab(self, page, context): with self.lock: page.goto("about:blank") # Clear the page by navigating to a blank page context.clear_cookies() self.tab_pool.put((page, context)) ================================================ FILE: packages/backend/config.yaml ================================================ core: openai_api_key: tag: "core" description: "API key for accessing OpenAI services." stabilityai_api_key: tag: "core" description: "API key for accessing Stability AI services." replicate_api_key: tag: "core" description: "API key for accessing Replicate services." extension: anthropic_api_key: tag: "core" description: "API key for accessing Anthropic services." deepseek_api_key: tag: "core" description: "API key for accessing DeepSeek services." openrouter_api_key: tag: "core" description: "API key for accessing OpenRouter services." ================================================ FILE: packages/backend/hooks/hook-app.processors.py ================================================ from PyInstaller.utils.hooks import collect_submodules hiddenimports = collect_submodules('app.processors') ================================================ FILE: packages/backend/pyproject.toml ================================================ [tool.poetry] name = "ai-flow-back" version = "0.11.3" description = "" authors = ["DahnM20 "] readme = "README.md" packages = [{ include = "app" }] [tool.poetry.dependencies] python = ">=3.9,<3.12" python-dotenv = "^1.0.0" openai = "1.76.0" flask = "^2.2.3" flask-socketio = "^5.3.3" flask-cors = "^3.0.10" unstructured = "^0.6.3" langchain = ">=0.0.303" python-magic = "^0.4.14" pytesseract = "^0.3.10" requests = "^2.31.0" tabulate = "^0.9.0" pdf2image = "^1.16.3" colorlog = "^6.7.0" eventlet = "^0.33.3" playwright = "1.39" youtube-transcript-api = "^0.6.1" pytube = "^15.0.0" boto3 = "^1.28.52" pyjwt = "^2.8.0" jwcrypto = "^1.5.0" python-jose = "^3.3.0" cachetools = "^5.3.1" flask-injector = "^0.15.0" setuptools = "^68.2.2" pypdf = "4.2.0" replicate = "0.22.0" tiktoken = "0.7.0" openapi-spec-validator = "^0.7.1" anthropic = "0.49.0" pymupdf = "^1.24.7" pydub = "^0.25.1" markdownify = "^1.1.0" beautifulsoup4 = "^4.13.3" [tool.poetry.group.dev.dependencies] datamodel-code-generator = "^0.25.5" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" ================================================ FILE: packages/backend/requirements_windows.txt ================================================ python-magic-bin ================================================ FILE: packages/backend/resources/data/openrouter_models.json ================================================ {"data":[{"id":"deepseek/deepseek-chat","name":"DeepSeek V3","created":1735241320,"description":"DeepSeek-V3 is the latest model from the DeepSeek team, building upon the instruction following and coding abilities of the previous versions. Pre-trained on nearly 15 trillion tokens, the reported evaluations reveal that the model outperforms other open-source models and rivals leading closed-source models. For model details, please visit [the DeepSeek-V3 repo](https://github.com/deepseek-ai/DeepSeek-V3) for more information.","context_length":64000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.00000014","completion":"0.00000028","image":"0","request":"0"},"top_provider":{"context_length":64000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qvq-72b-preview","name":"Qwen: QvQ 72B Preview","created":1735088567,"description":"QVQ-72B-Preview is an experimental research model developed by the [Qwen](/qwen) team, focusing on enhancing visual reasoning capabilities.\n\n## Performance\n\n| | **QVQ-72B-Preview** | o1-2024-12-17 | gpt-4o-2024-05-13 | Claude3.5 Sonnet-20241022 | Qwen2VL-72B |\n|----------------|-----------------|---------------|-------------------|----------------------------|-------------|\n| MMMU(val) | 70.3 | 77.3 | 69.1 | 70.4 | 64.5 |\n| MathVista(mini) | 71.4 | 71.0 | 63.8 | 65.3 | 70.5 |\n| MathVision(full) | 35.9 | – | 30.4 | 35.6 | 25.9 |\n| OlympiadBench | 20.4 | – | 25.9 | – | 11.2 |\n\n\n## Limitations\n\n1. **Language Mixing and Code-Switching:** The model might occasionally mix different languages or unexpectedly switch between them, potentially affecting the clarity of its responses.\n2. **Recursive Reasoning Loops:** There's a risk of the model getting caught in recursive reasoning loops, leading to lengthy responses that may not even arrive at a final answer.\n3. **Safety and Ethical Considerations:** Robust safety measures are needed to ensure reliable and safe performance. Users should exercise caution when deploying this model.\n4. **Performance and Benchmark Limitations:** Despite the improvements in visual reasoning, QVQ doesn’t entirely replace the capabilities of [Qwen2-VL-72B](/qwen/qwen-2-vl-72b-instruct). During multi-step visual reasoning, the model might gradually lose focus on the image content, leading to hallucinations. Moreover, QVQ doesn’t show significant improvement over [Qwen2-VL-72B](/qwen/qwen-2-vl-72b-instruct) in basic recognition tasks like identifying people, animals, or plants.\n\nNote: Currently, the model only supports single-round dialogues and image outputs. It does not support video inputs.","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"Qwen","instruct_type":null},"pricing":{"prompt":"0.00000025","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-2.0-flash-thinking-exp:free","name":"Google: Gemini 2.0 Flash Thinking Experimental (free)","created":1734650026,"description":"Gemini 2.0 Flash Thinking Mode is an experimental model that's trained to generate the \"thinking process\" the model goes through as part of its response. As a result, Thinking Mode is capable of stronger reasoning capabilities in its responses than the [base Gemini 2.0 Flash model](/google/gemini-2.0-flash-exp).","context_length":40000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":40000,"max_completion_tokens":8000,"is_moderated":false},"per_request_limits":null},{"id":"sao10k/l3.3-euryale-70b","name":"Sao10K: Llama 3.3 Euryale 70B","created":1734535928,"description":"Euryale L3.3 70B is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k). It is the successor of [Euryale L3 70B v2.2](/models/sao10k/l3-euryale-70b).","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000015","completion":"0.0000015","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"inflatebot/mn-mag-mell-r1","name":"Inflatebot: Mag Mell R1 12B","created":1734535439,"description":"Mag Mell is a merge of pre-trained language models created using mergekit, based on [Mistral Nemo](/mistralai/mistral-nemo). It is a great roleplay and storytelling model which combines the best parts of many other models to be a general purpose solution for many usecases.\n\nIntended to be a general purpose \"Best of Nemo\" model for any fictional, creative use case. \n\nMag Mell is composed of 3 intermediate parts:\n- Hero (RP, trope coverage)\n- Monk (Intelligence, groundedness)\n- Deity (Prose, flair)","context_length":16000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.0000009","completion":"0.0000009","image":"0","request":"0"},"top_provider":{"context_length":16000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/o1","name":"OpenAI: o1","created":1734459999,"description":"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding. The o1 model series is trained with large-scale reinforcement learning to reason using chain of thought. \n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000015","completion":"0.00006","image":"0.021675","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":100000,"is_moderated":true},"per_request_limits":null},{"id":"eva-unit-01/eva-llama-3.33-70b","name":"EVA Llama 3.33 70b","created":1734377303,"description":"EVA Llama 3.33 70b is a roleplay and storywriting specialist model. It is a full-parameter finetune of [Llama-3.3-70B-Instruct](https://openrouter.ai/meta-llama/llama-3.3-70b-instruct) on mixture of synthetic and natural data.\n\nIt uses Celeste 70B 0.1 data mixture, greatly expanding it to improve versatility, creativity and \"flavor\" of the resulting model\n\nThis model was built with Llama by Meta.\n","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000004","completion":"0.000006","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"x-ai/grok-2-vision-1212","name":"xAI: Grok 2 Vision 1212","created":1734237338,"description":"Grok 2 Vision 1212 advances image-based AI with stronger visual comprehension, refined instruction-following, and multilingual support. From object recognition to style analysis, it empowers developers to build more intuitive, visually aware applications. Its enhanced steerability and reasoning establish a robust foundation for next-generation image solutions.\n\nTo read more about this model, check out [xAI's announcement](https://x.ai/blog/grok-1212).","context_length":32768,"architecture":{"modality":"text+image->text","tokenizer":"Grok","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.00001","image":"0.0036","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"x-ai/grok-2-1212","name":"xAI: Grok 2 1212","created":1734232814,"description":"Grok 2 1212 introduces significant enhancements to accuracy, instruction adherence, and multilingual support, making it a powerful and flexible choice for developers seeking a highly steerable, intelligent model.","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Grok","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.00001","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command-r7b-12-2024","name":"Cohere: Command R7B (12-2024)","created":1734158152,"description":"Command R7B (12-2024) is a small, fast update of the Command R+ model, delivered in December 2024. It excels at RAG, tool use, agents, and similar tasks requiring complex reasoning and multiple steps.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.0000000375","completion":"0.00000015","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-2.0-flash-exp:free","name":"Google: Gemini Flash 2.0 Experimental (free)","created":1733937523,"description":"Gemini Flash 2.0 offers a significantly faster time to first token (TTFT) compared to [Gemini Flash 1.5](google/gemini-flash-1.5), while maintaining quality on par with larger models like [Gemini Pro 1.5](google/gemini-pro-1.5). It introduces notable enhancements in multimodal understanding, coding capabilities, complex instruction following, and function calling. These advancements come together to deliver more seamless and robust agentic experiences.","context_length":1048576,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":1048576,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-exp-1206:free","name":"Google: Gemini Experimental 1206 (free)","created":1733507713,"description":"Experimental release (December 6, 2024) of Gemini.","context_length":2097152,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":2097152,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.3-70b-instruct","name":"Meta: Llama 3.3 70B Instruct","created":1733506137,"description":"The Meta Llama 3.3 multilingual large language model (LLM) is a pretrained and instruction tuned generative model in 70B (text in/text out). The Llama 3.3 instruction tuned text only model is optimized for multilingual dialogue use cases and outperforms many of the available open source and closed chat models on common industry benchmarks.\n\nSupported languages: English, German, French, Italian, Portuguese, Hindi, Spanish, and Thai.\n\n[Model Card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_3/MODEL_CARD.md)","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000012","completion":"0.0000003","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"amazon/nova-lite-v1","name":"Amazon: Nova Lite 1.0","created":1733437363,"description":"Amazon Nova Lite 1.0 is a very low-cost multimodal model from Amazon that focused on fast processing of image, video, and text inputs to generate text output. Amazon Nova Lite can handle real-time customer interactions, document analysis, and visual question-answering tasks with high accuracy.\n\nWith an input context of 300K tokens, it can analyze multiple images or up to 30 minutes of video in a single input.","context_length":300000,"architecture":{"modality":"text+image->text","tokenizer":"Nova","instruct_type":null},"pricing":{"prompt":"0.00000006","completion":"0.00000024","image":"0.00009","request":"0"},"top_provider":{"context_length":300000,"max_completion_tokens":5120,"is_moderated":false},"per_request_limits":null},{"id":"amazon/nova-micro-v1","name":"Amazon: Nova Micro 1.0","created":1733437237,"description":"Amazon Nova Micro 1.0 is a text-only model that delivers the lowest latency responses in the Amazon Nova family of models at a very low cost. With a context length of 128K tokens and optimized for speed and cost, Amazon Nova Micro excels at tasks such as text summarization, translation, content classification, interactive chat, and brainstorming. It has simple mathematical reasoning and coding abilities.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Nova","instruct_type":null},"pricing":{"prompt":"0.000000035","completion":"0.00000014","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":5120,"is_moderated":false},"per_request_limits":null},{"id":"amazon/nova-pro-v1","name":"Amazon: Nova Pro 1.0","created":1733436303,"description":"Amazon Nova Pro 1.0 is a capable multimodal model from Amazon focused on providing a combination of accuracy, speed, and cost for a wide range of tasks. As of December 2024, it achieves state-of-the-art performance on key benchmarks including visual question answering (TextVQA) and video understanding (VATEX).\n\nAmazon Nova Pro demonstrates strong capabilities in processing both visual and textual information and at analyzing financial documents.\n\n**NOTE**: Video input is not supported at this time.","context_length":300000,"architecture":{"modality":"text+image->text","tokenizer":"Nova","instruct_type":null},"pricing":{"prompt":"0.0000008","completion":"0.0000032","image":"0.0012","request":"0"},"top_provider":{"context_length":300000,"max_completion_tokens":5120,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwq-32b-preview","name":"Qwen: QwQ 32B Preview","created":1732754541,"description":"QwQ-32B-Preview is an experimental research model focused on AI reasoning capabilities developed by the Qwen Team. As a preview release, it demonstrates promising analytical abilities while having several important limitations:\n\n1. **Language Mixing and Code-Switching**: The model may mix languages or switch between them unexpectedly, affecting response clarity.\n2. **Recursive Reasoning Loops**: The model may enter circular reasoning patterns, leading to lengthy responses without a conclusive answer.\n3. **Safety and Ethical Considerations**: The model requires enhanced safety measures to ensure reliable and secure performance, and users should exercise caution when deploying it.\n4. **Performance and Benchmark Limitations**: The model excels in math and coding but has room for improvement in other areas, such as common sense reasoning and nuanced language understanding.\n\n","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":null},"pricing":{"prompt":"0.00000012","completion":"0.00000018","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-exp-1121:free","name":"Google: Gemini Experimental 1121 (free)","created":1732216725,"description":"Experimental release (November 21st, 2024) of Gemini.","context_length":40960,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":40960,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"google/learnlm-1.5-pro-experimental:free","name":"Google: LearnLM 1.5 Pro Experimental (free)","created":1732216551,"description":"An experimental version of [Gemini 1.5 Pro](/google/gemini-pro-1.5) from Google.","context_length":40960,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":40960,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"eva-unit-01/eva-qwen-2.5-72b","name":"EVA Qwen2.5 72B","created":1732210606,"description":"EVA Qwen2.5 72B is a roleplay and storywriting specialist model. It's a full-parameter finetune of Qwen2.5-72B on mixture of synthetic and natural data.\n\nIt uses Celeste 70B 0.1 data mixture, greatly expanding it to improve versatility, creativity and \"flavor\" of the resulting model.","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.000004","completion":"0.000006","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4o-2024-11-20","name":"OpenAI: GPT-4o (2024-11-20)","created":1732127594,"description":"The 2024-11-20 version of GPT-4o offers a leveled-up creative writing ability with more natural, engaging, and tailored writing to improve relevance & readability. It’s also better at working with uploaded files, providing deeper insights & more thorough responses.\n\nGPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.0000025","completion":"0.00001","image":"0.003613","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"mistralai/mistral-large-2411","name":"Mistral Large 2411","created":1731978685,"description":"Mistral Large 2 2411 is an update of [Mistral Large 2](/mistralai/mistral-large) released together with [Pixtral Large 2411](/mistralai/pixtral-large-2411)\n\nIt provides a significant upgrade on the previous [Mistral Large 24.07](/mistralai/mistral-large-2407), with notable improvements in long context understanding, a new system prompt, and more accurate function calling.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000006","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-large-2407","name":"Mistral Large 2407","created":1731978415,"description":"This is Mistral AI's flagship model, Mistral Large 2 (version mistral-large-2407). It's a proprietary weights-available model and excels at reasoning, code, JSON, chat, and more. Read the launch announcement [here](https://mistral.ai/news/mistral-large-2407/).\n\nIt supports dozens of languages including French, German, Spanish, Italian, Portuguese, Arabic, Hindi, Russian, Chinese, Japanese, and Korean, along with 80+ coding languages including Python, Java, C, C++, JavaScript, and Bash. Its long context window allows precise information recall from large documents.\n","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000006","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/pixtral-large-2411","name":"Mistral: Pixtral Large 2411","created":1731977388,"description":"Pixtral Large is a 124B parameter, open-weight, multimodal model built on top of [Mistral Large 2](/mistralai/mistral-large-2411). The model is able to understand documents, charts and natural images.\n\nThe model is available under the Mistral Research License (MRL) for research and educational use, and the Mistral Commercial License for experimentation, testing, and production for commercial purposes.\n\n","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000006","image":"0.002888","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"x-ai/grok-vision-beta","name":"xAI: Grok Vision Beta","created":1731976624,"description":"Grok Vision Beta is xAI's experimental language model with vision capability.\n\n","context_length":8192,"architecture":{"modality":"text+image->text","tokenizer":"Grok","instruct_type":null},"pricing":{"prompt":"0.000005","completion":"0.000015","image":"0.009","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-exp-1114:free","name":"Google: Gemini Experimental 1114 (free)","created":1731714740,"description":"Gemini 11-14 (2024) experimental model features \"quality\" improvements.","context_length":40960,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":40960,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"infermatic/mn-inferor-12b","name":"Infermatic: Mistral Nemo Inferor 12B","created":1731464428,"description":"Inferor 12B is a merge of top roleplay models, expert on immersive narratives and storytelling.\n\nThis model was merged using the [Model Stock](https://arxiv.org/abs/2403.19522) merge method using [anthracite-org/magnum-v4-12b](https://openrouter.ai/anthracite-org/magnum-v4-72b) as a base.\n","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000025","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2.5-coder-32b-instruct","name":"Qwen2.5 Coder 32B Instruct","created":1731368400,"description":"Qwen2.5-Coder is the latest series of Code-Specific Qwen large language models (formerly known as CodeQwen). Qwen2.5-Coder brings the following improvements upon CodeQwen1.5:\n\n- Significantly improvements in **code generation**, **code reasoning** and **code fixing**. \n- A more comprehensive foundation for real-world applications such as **Code Agents**. Not only enhancing coding capabilities but also maintaining its strengths in mathematics and general competencies.\n\nTo read more about its evaluation results, check out [Qwen 2.5 Coder's blog](https://qwenlm.github.io/blog/qwen2.5-coder-family/).","context_length":33000,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.00000007","completion":"0.00000016","image":"0","request":"0"},"top_provider":{"context_length":33000,"max_completion_tokens":3000,"is_moderated":false},"per_request_limits":null},{"id":"raifle/sorcererlm-8x22b","name":"SorcererLM 8x22B","created":1731105083,"description":"SorcererLM is an advanced RP and storytelling model, built as a Low-rank 16-bit LoRA fine-tuned on [WizardLM-2 8x22B](/microsoft/wizardlm-2-8x22b).\n\n- Advanced reasoning and emotional intelligence for engaging and immersive interactions\n- Vivid writing capabilities enriched with spatial and contextual awareness\n- Enhanced narrative depth, promoting creative and dynamic storytelling","context_length":16000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"vicuna"},"pricing":{"prompt":"0.0000045","completion":"0.0000045","image":"0","request":"0"},"top_provider":{"context_length":16000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"eva-unit-01/eva-qwen-2.5-32b","name":"EVA Qwen2.5 32B","created":1731104847,"description":"EVA Qwen2.5 32B is a roleplaying/storywriting specialist model. It's a full-parameter finetune of Qwen2.5-32B on mixture of synthetic and natural data.\n\nIt uses Celeste 70B 0.1 data mixture, greatly expanding it to improve versatility, creativity and \"flavor\" of the resulting model.","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.0000026","completion":"0.0000034","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"thedrummer/unslopnemo-12b","name":"Unslopnemo 12b","created":1731103448,"description":"UnslopNemo v4.1 is the latest addition from the creator of Rocinante, designed for adventure writing and role-play scenarios.","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.0000005","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-haiku-20241022:beta","name":"Anthropic: Claude 3.5 Haiku (2024-10-22) (self-moderated)","created":1730678400,"description":"Claude 3.5 Haiku features enhancements across all skill sets including coding, tool use, and reasoning. As the fastest model in the Anthropic lineup, it offers rapid response times suitable for applications that require high interactivity and low latency, such as user-facing chatbots and on-the-fly code completions. It also excels in specialized tasks like data extraction and real-time content moderation, making it a versatile tool for a broad range of industries.\n\nIt does not support image inputs.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/3-5-models-and-computer-use)","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.0000008","completion":"0.000004","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-haiku-20241022","name":"Anthropic: Claude 3.5 Haiku (2024-10-22)","created":1730678400,"description":"Claude 3.5 Haiku features enhancements across all skill sets including coding, tool use, and reasoning. As the fastest model in the Anthropic lineup, it offers rapid response times suitable for applications that require high interactivity and low latency, such as user-facing chatbots and on-the-fly code completions. It also excels in specialized tasks like data extraction and real-time content moderation, making it a versatile tool for a broad range of industries.\n\nIt does not support image inputs.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/3-5-models-and-computer-use)","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.0000008","completion":"0.000004","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":true},"per_request_limits":null},{"id":"anthropic/claude-3.5-haiku:beta","name":"Anthropic: Claude 3.5 Haiku (self-moderated)","created":1730678400,"description":"Claude 3.5 Haiku features offers enhanced capabilities in speed, coding accuracy, and tool use. Engineered to excel in real-time applications, it delivers quick response times that are essential for dynamic tasks such as chat interactions and immediate coding suggestions.\n\nThis makes it highly suitable for environments that demand both speed and precision, such as software development, customer service bots, and data management systems.\n\nThis model is currently pointing to [Claude 3.5 Haiku (2024-10-22)](/anthropic/claude-3-5-haiku-20241022).","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.0000008","completion":"0.000004","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-haiku","name":"Anthropic: Claude 3.5 Haiku","created":1730678400,"description":"Claude 3.5 Haiku features offers enhanced capabilities in speed, coding accuracy, and tool use. Engineered to excel in real-time applications, it delivers quick response times that are essential for dynamic tasks such as chat interactions and immediate coding suggestions.\n\nThis makes it highly suitable for environments that demand both speed and precision, such as software development, customer service bots, and data management systems.\n\nThis model is currently pointing to [Claude 3.5 Haiku (2024-10-22)](/anthropic/claude-3-5-haiku-20241022).","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.0000008","completion":"0.000004","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":true},"per_request_limits":null},{"id":"neversleep/llama-3.1-lumimaid-70b","name":"NeverSleep: Lumimaid v0.2 70B","created":1729555200,"description":"Lumimaid v0.2 70B is a finetune of [Llama 3.1 70B](/meta-llama/llama-3.1-70b-instruct) with a \"HUGE step up dataset wise\" compared to Lumimaid v0.1. Sloppy chats output were purged.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000003375","completion":"0.0000045","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"anthracite-org/magnum-v4-72b","name":"Magnum v4 72B","created":1729555200,"description":"This is a series of models designed to replicate the prose quality of the Claude 3 models, specifically Sonnet(https://openrouter.ai/anthropic/claude-3.5-sonnet) and Opus(https://openrouter.ai/anthropic/claude-3-opus).\n\nThe model is fine-tuned on top of [Qwen2.5 72B](https://openrouter.ai/qwen/qwen-2.5-72b-instruct).","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.000001875","completion":"0.00000225","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":1024,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-sonnet:beta","name":"Anthropic: Claude 3.5 Sonnet (self-moderated)","created":1729555200,"description":"New Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-sonnet","name":"Anthropic: Claude 3.5 Sonnet","created":1729555200,"description":"New Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":true},"per_request_limits":null},{"id":"x-ai/grok-beta","name":"xAI: Grok Beta","created":1729382400,"description":"Grok Beta is xAI's experimental language model with state-of-the-art reasoning capabilities, best for complex and multi-step use cases.\n\nIt is the successor of [Grok 2](https://x.ai/blog/grok-2) with enhanced context length.","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Grok","instruct_type":null},"pricing":{"prompt":"0.000005","completion":"0.000015","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/ministral-8b","name":"Mistral: Ministral 8B","created":1729123200,"description":"Ministral 8B is an 8B parameter model featuring a unique interleaved sliding-window attention pattern for faster, memory-efficient inference. Designed for edge use cases, it supports up to 128k context length and excels in knowledge and reasoning tasks. It outperforms peers in the sub-10B category, making it perfect for low-latency, privacy-first applications.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.0000001","completion":"0.0000001","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/ministral-3b","name":"Mistral: Ministral 3B","created":1729123200,"description":"Ministral 3B is a 3B parameter model optimized for on-device and edge computing. It excels in knowledge, commonsense reasoning, and function-calling, outperforming larger models like Mistral 7B on most benchmarks. Supporting up to 128k context length, it’s ideal for orchestrating agentic workflows and specialist tasks with efficient inference.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.00000004","completion":"0.00000004","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2.5-7b-instruct","name":"Qwen2.5 7B Instruct","created":1729036800,"description":"Qwen2.5 7B is the latest series of Qwen large language models. Qwen2.5 brings the following improvements upon Qwen2:\n\n- Significantly more knowledge and has greatly improved capabilities in coding and mathematics, thanks to our specialized expert models in these domains.\n\n- Significant improvements in instruction following, generating long texts (over 8K tokens), understanding structured data (e.g, tables), and generating structured outputs especially JSON. More resilient to the diversity of system prompts, enhancing role-play implementation and condition-setting for chatbots.\n\n- Long-context Support up to 128K tokens and can generate up to 8K tokens.\n\n- Multilingual support for over 29 languages, including Chinese, English, French, Spanish, Portuguese, German, Italian, Russian, Japanese, Korean, Vietnamese, Thai, Arabic, and more.\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.00000027","completion":"0.00000027","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"nvidia/llama-3.1-nemotron-70b-instruct","name":"NVIDIA: Llama 3.1 Nemotron 70B Instruct","created":1728950400,"description":"NVIDIA's Llama 3.1 Nemotron 70B is a language model designed for generating precise and useful responses. Leveraging [Llama 3.1 70B](/models/meta-llama/llama-3.1-70b-instruct) architecture and Reinforcement Learning from Human Feedback (RLHF), it excels in automatic alignment benchmarks. This model is tailored for applications requiring high accuracy in helpfulness and response generation, suitable for diverse user queries across multiple domains.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":131000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000012","completion":"0.0000003","image":"0","request":"0"},"top_provider":{"context_length":131000,"max_completion_tokens":131000,"is_moderated":false},"per_request_limits":null},{"id":"inflection/inflection-3-pi","name":"Inflection: Inflection 3 Pi","created":1728604800,"description":"Inflection 3 Pi powers Inflection's [Pi](https://pi.ai) chatbot, including backstory, emotional intelligence, productivity, and safety. It has access to recent news, and excels in scenarios like customer support and roleplay.\n\nPi has been trained to mirror your tone and style, if you use more emojis, so will Pi! Try experimenting with various prompts and conversation styles.","context_length":8000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.0000025","completion":"0.00001","image":"0","request":"0"},"top_provider":{"context_length":8000,"max_completion_tokens":1024,"is_moderated":false},"per_request_limits":null},{"id":"inflection/inflection-3-productivity","name":"Inflection: Inflection 3 Productivity","created":1728604800,"description":"Inflection 3 Productivity is optimized for following instructions. It is better for tasks requiring JSON output or precise adherence to provided guidelines. It has access to recent news.\n\nFor emotional intelligence similar to Pi, see [Inflect 3 Pi](/inflection/inflection-3-pi)\n\nSee [Inflection's announcement](https://inflection.ai/blog/enterprise) for more details.","context_length":8000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.0000025","completion":"0.00001","image":"0","request":"0"},"top_provider":{"context_length":8000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-flash-1.5-8b","name":"Google: Gemini Flash 1.5 8B","created":1727913600,"description":"Gemini Flash 1.5 8B is optimized for speed and efficiency, offering enhanced performance in small prompt tasks like chat, transcription, and translation. With reduced latency, it is highly effective for real-time and large-scale operations. This model focuses on cost-effective solutions while maintaining high-quality results.\n\n[Click here to learn more about this model](https://developers.googleblog.com/en/gemini-15-flash-8b-is-now-generally-available-for-use/).\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).","context_length":1000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0.0000000375","completion":"0.00000015","image":"0","request":"0"},"top_provider":{"context_length":1000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"anthracite-org/magnum-v2-72b","name":"Magnum v2 72B","created":1727654400,"description":"From the maker of [Goliath](https://openrouter.ai/models/alpindale/goliath-120b), Magnum 72B is the seventh in a family of models designed to achieve the prose quality of the Claude 3 models, notably Opus & Sonnet.\n\nThe model is based on [Qwen2 72B](https://openrouter.ai/models/qwen/qwen-2-72b-instruct) and trained with 55 million tokens of highly curated roleplay (RP) data.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.000003","completion":"0.000003","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"liquid/lfm-40b","name":"Liquid: LFM 40B MoE","created":1727654400,"description":"Liquid's 40.3B Mixture of Experts (MoE) model. Liquid Foundation Models (LFMs) are large neural networks built with computational units rooted in dynamic systems.\n\nLFMs are general-purpose AI models that can be used to model any kind of sequential data, including video, audio, text, time series, and signals.\n\nSee the [launch announcement](https://www.liquid.ai/liquid-foundation-models) for benchmarks and more info.","context_length":66000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"vicuna"},"pricing":{"prompt":"0.00000015","completion":"0.00000015","image":"0","request":"0"},"top_provider":{"context_length":66000,"max_completion_tokens":66000,"is_moderated":false},"per_request_limits":null},{"id":"thedrummer/rocinante-12b","name":"Rocinante 12B","created":1727654400,"description":"Rocinante 12B is designed for engaging storytelling and rich prose.\n\nEarly testers have reported:\n- Expanded vocabulary with unique and expressive word choices\n- Enhanced creativity for vivid narratives\n- Adventure-filled and captivating stories","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.00000025","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-3b-instruct:free","name":"Meta: Llama 3.2 3B Instruct (free)","created":1727222400,"description":"Llama 3.2 3B is a 3-billion-parameter multilingual large language model, optimized for advanced natural language processing tasks like dialogue generation, reasoning, and summarization. Designed with the latest transformer architecture, it supports eight languages, including English, Spanish, and Hindi, and is adaptable for additional languages.\n\nTrained on 9 trillion tokens, the Llama 3.2 3B model excels in instruction-following, complex reasoning, and tool use. Its balanced performance makes it ideal for applications needing accuracy and efficiency in text generation across multilingual settings.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-3b-instruct","name":"Meta: Llama 3.2 3B Instruct","created":1727222400,"description":"Llama 3.2 3B is a 3-billion-parameter multilingual large language model, optimized for advanced natural language processing tasks like dialogue generation, reasoning, and summarization. Designed with the latest transformer architecture, it supports eight languages, including English, Spanish, and Hindi, and is adaptable for additional languages.\n\nTrained on 9 trillion tokens, the Llama 3.2 3B model excels in instruction-following, complex reasoning, and tool use. Its balanced performance makes it ideal for applications needing accuracy and efficiency in text generation across multilingual settings.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":131000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000000015","completion":"0.000000025","image":"0","request":"0"},"top_provider":{"context_length":131000,"max_completion_tokens":131000,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-1b-instruct:free","name":"Meta: Llama 3.2 1B Instruct (free)","created":1727222400,"description":"Llama 3.2 1B is a 1-billion-parameter language model focused on efficiently performing natural language tasks, such as summarization, dialogue, and multilingual text analysis. Its smaller size allows it to operate efficiently in low-resource environments while maintaining strong task performance.\n\nSupporting eight core languages and fine-tunable for more, Llama 1.3B is ideal for businesses or developers seeking lightweight yet powerful AI solutions that can operate in diverse multilingual settings without the high computational demand of larger models.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-1b-instruct","name":"Meta: Llama 3.2 1B Instruct","created":1727222400,"description":"Llama 3.2 1B is a 1-billion-parameter language model focused on efficiently performing natural language tasks, such as summarization, dialogue, and multilingual text analysis. Its smaller size allows it to operate efficiently in low-resource environments while maintaining strong task performance.\n\nSupporting eight core languages and fine-tunable for more, Llama 1.3B is ideal for businesses or developers seeking lightweight yet powerful AI solutions that can operate in diverse multilingual settings without the high computational demand of larger models.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000001","completion":"0.00000001","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-90b-vision-instruct:free","name":"Meta: Llama 3.2 90B Vision Instruct (free)","created":1727222400,"description":"The Llama 90B Vision model is a top-tier, 90-billion-parameter multimodal model designed for the most challenging visual reasoning and language tasks. It offers unparalleled accuracy in image captioning, visual question answering, and advanced image-text comprehension. Pre-trained on vast multimodal datasets and fine-tuned with human feedback, the Llama 90B Vision is engineered to handle the most demanding image-based AI tasks.\n\nThis model is perfect for industries requiring cutting-edge multimodal AI capabilities, particularly those dealing with complex, real-time visual and textual analysis.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":4096,"architecture":{"modality":"text+image->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-90b-vision-instruct","name":"Meta: Llama 3.2 90B Vision Instruct","created":1727222400,"description":"The Llama 90B Vision model is a top-tier, 90-billion-parameter multimodal model designed for the most challenging visual reasoning and language tasks. It offers unparalleled accuracy in image captioning, visual question answering, and advanced image-text comprehension. Pre-trained on vast multimodal datasets and fine-tuned with human feedback, the Llama 90B Vision is engineered to handle the most demanding image-based AI tasks.\n\nThis model is perfect for industries requiring cutting-edge multimodal AI capabilities, particularly those dealing with complex, real-time visual and textual analysis.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":131072,"architecture":{"modality":"text+image->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000009","completion":"0.0000009","image":"0.001301","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-11b-vision-instruct:free","name":"Meta: Llama 3.2 11B Vision Instruct (free)","created":1727222400,"description":"Llama 3.2 11B Vision is a multimodal model with 11 billion parameters, designed to handle tasks combining visual and textual data. It excels in tasks such as image captioning and visual question answering, bridging the gap between language generation and visual reasoning. Pre-trained on a massive dataset of image-text pairs, it performs well in complex, high-accuracy image analysis.\n\nIts ability to integrate visual understanding with language processing makes it an ideal solution for industries requiring comprehensive visual-linguistic AI applications, such as content creation, AI-driven customer service, and research.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text+image->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.2-11b-vision-instruct","name":"Meta: Llama 3.2 11B Vision Instruct","created":1727222400,"description":"Llama 3.2 11B Vision is a multimodal model with 11 billion parameters, designed to handle tasks combining visual and textual data. It excels in tasks such as image captioning and visual question answering, bridging the gap between language generation and visual reasoning. Pre-trained on a massive dataset of image-text pairs, it performs well in complex, high-accuracy image analysis.\n\nIts ability to integrate visual understanding with language processing makes it an ideal solution for industries requiring comprehensive visual-linguistic AI applications, such as content creation, AI-driven customer service, and research.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).","context_length":131072,"architecture":{"modality":"text+image->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000000055","completion":"0.000000055","image":"0.00007948","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2.5-72b-instruct","name":"Qwen2.5 72B Instruct","created":1726704000,"description":"Qwen2.5 72B is the latest series of Qwen large language models. Qwen2.5 brings the following improvements upon Qwen2:\n\n- Significantly more knowledge and has greatly improved capabilities in coding and mathematics, thanks to our specialized expert models in these domains.\n\n- Significant improvements in instruction following, generating long texts (over 8K tokens), understanding structured data (e.g, tables), and generating structured outputs especially JSON. More resilient to the diversity of system prompts, enhancing role-play implementation and condition-setting for chatbots.\n\n- Long-context Support up to 128K tokens and can generate up to 8K tokens.\n\n- Multilingual support for over 29 languages, including Chinese, English, French, Spanish, Portuguese, German, Italian, Russian, Japanese, Korean, Vietnamese, Thai, Arabic, and more.\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.00000023","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2-vl-72b-instruct","name":"Qwen2-VL 72B Instruct","created":1726617600,"description":"Qwen2 VL 72B is a multimodal LLM from the Qwen Team with the following key enhancements:\n\n- SoTA understanding of images of various resolution & ratio: Qwen2-VL achieves state-of-the-art performance on visual understanding benchmarks, including MathVista, DocVQA, RealWorldQA, MTVQA, etc.\n\n- Understanding videos of 20min+: Qwen2-VL can understand videos over 20 minutes for high-quality video-based question answering, dialog, content creation, etc.\n\n- Agent that can operate your mobiles, robots, etc.: with the abilities of complex reasoning and decision making, Qwen2-VL can be integrated with devices like mobile phones, robots, etc., for automatic operation based on visual environment and text instructions.\n\n- Multilingual Support: to serve global users, besides English and Chinese, Qwen2-VL now supports the understanding of texts in different languages inside images, including most European languages, Japanese, Korean, Arabic, Vietnamese, etc.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2-vl/) and [GitHub repo](https://github.com/QwenLM/Qwen2-VL).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":4096,"architecture":{"modality":"text+image->text","tokenizer":"Qwen","instruct_type":null},"pricing":{"prompt":"0.0000004","completion":"0.0000004","image":"0.000578","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"neversleep/llama-3.1-lumimaid-8b","name":"NeverSleep: Lumimaid v0.2 8B","created":1726358400,"description":"Lumimaid v0.2 8B is a finetune of [Llama 3.1 8B](/models/meta-llama/llama-3.1-8b-instruct) with a \"HUGE step up dataset wise\" compared to Lumimaid v0.1. Sloppy chats output were purged.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000001875","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"openai/o1-mini-2024-09-12","name":"OpenAI: o1-mini (2024-09-12)","created":1726099200,"description":"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000012","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":65536,"is_moderated":true},"per_request_limits":null},{"id":"openai/o1-preview","name":"OpenAI: o1-preview","created":1726099200,"description":"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000015","completion":"0.00006","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":32768,"is_moderated":true},"per_request_limits":null},{"id":"openai/o1-preview-2024-09-12","name":"OpenAI: o1-preview (2024-09-12)","created":1726099200,"description":"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000015","completion":"0.00006","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":32768,"is_moderated":true},"per_request_limits":null},{"id":"openai/o1-mini","name":"OpenAI: o1-mini","created":1726099200,"description":"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000012","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":65536,"is_moderated":true},"per_request_limits":null},{"id":"mistralai/pixtral-12b","name":"Mistral: Pixtral 12B","created":1725926400,"description":"The first multi-modal, text+image-to-text model from Mistral AI. Its weights were launched via torrent: https://x.com/mistralai/status/1833758285167722836.","context_length":4096,"architecture":{"modality":"text+image->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.0000001","completion":"0.0000001","image":"0.0001445","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command-r-08-2024","name":"Cohere: Command R (08-2024)","created":1724976000,"description":"command-r-08-2024 is an update of the [Command R](/models/cohere/command-r) with improved performance for multilingual retrieval-augmented generation (RAG) and tool use. More broadly, it is better at math, code and reasoning and is competitive with the previous version of the larger Command R+ model.\n\nRead the launch post [here](https://docs.cohere.com/changelog/command-gets-refreshed).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.0000001425","completion":"0.00000057","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command-r-plus-08-2024","name":"Cohere: Command R+ (08-2024)","created":1724976000,"description":"command-r-plus-08-2024 is an update of the [Command R+](/models/cohere/command-r-plus) with roughly 50% higher throughput and 25% lower latencies as compared to the previous Command R+ version, while keeping the hardware footprint the same.\n\nRead the launch post [here](https://docs.cohere.com/changelog/command-gets-refreshed).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.000002375","completion":"0.0000095","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2-vl-7b-instruct","name":"Qwen2-VL 7B Instruct","created":1724803200,"description":"Qwen2 VL 7B is a multimodal LLM from the Qwen Team with the following key enhancements:\n\n- SoTA understanding of images of various resolution & ratio: Qwen2-VL achieves state-of-the-art performance on visual understanding benchmarks, including MathVista, DocVQA, RealWorldQA, MTVQA, etc.\n\n- Understanding videos of 20min+: Qwen2-VL can understand videos over 20 minutes for high-quality video-based question answering, dialog, content creation, etc.\n\n- Agent that can operate your mobiles, robots, etc.: with the abilities of complex reasoning and decision making, Qwen2-VL can be integrated with devices like mobile phones, robots, etc., for automatic operation based on visual environment and text instructions.\n\n- Multilingual Support: to serve global users, besides English and Chinese, Qwen2-VL now supports the understanding of texts in different languages inside images, including most European languages, Japanese, Korean, Arabic, Vietnamese, etc.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2-vl/) and [GitHub repo](https://github.com/QwenLM/Qwen2-VL).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":4096,"architecture":{"modality":"text+image->text","tokenizer":"Qwen","instruct_type":null},"pricing":{"prompt":"0.0000001","completion":"0.0000001","image":"0.0001445","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-flash-1.5-exp","name":"Google: Gemini Flash 1.5 Experimental","created":1724803200,"description":"Gemini 1.5 Flash Experimental is an experimental version of the [Gemini 1.5 Flash](/models/google/gemini-flash-1.5) model.\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n#multimodal\n\nNote: This model is experimental and not suited for production use-cases. It may be removed or redirected to another model in the future.","context_length":1000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":1000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"sao10k/l3.1-euryale-70b","name":"Sao10K: Llama 3.1 Euryale 70B v2.2","created":1724803200,"description":"Euryale L3.1 70B v2.2 is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k). It is the successor of [Euryale L3 70B v2.1](/models/sao10k/l3-euryale-70b).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000035","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-flash-1.5-8b-exp","name":"Google: Gemini Flash 1.5 8B Experimental","created":1724803200,"description":"Gemini Flash 1.5 8B Experimental is an experimental, 8B parameter version of the [Gemini Flash 1.5](/models/google/gemini-flash-1.5) model.\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n#multimodal\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.","context_length":1000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":1000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"ai21/jamba-1-5-large","name":"AI21: Jamba 1.5 Large","created":1724371200,"description":"Jamba 1.5 Large is part of AI21's new family of open models, offering superior speed, efficiency, and quality.\n\nIt features a 256K effective context window, the longest among open models, enabling improved performance on tasks like document summarization and analysis.\n\nBuilt on a novel SSM-Transformer architecture, it outperforms larger models like Llama 3.1 70B on benchmarks while maintaining resource efficiency.\n\nRead their [announcement](https://www.ai21.com/blog/announcing-jamba-model-family) to learn more.","context_length":256000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000008","image":"0","request":"0"},"top_provider":{"context_length":256000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"ai21/jamba-1-5-mini","name":"AI21: Jamba 1.5 Mini","created":1724371200,"description":"Jamba 1.5 Mini is the world's first production-grade Mamba-based model, combining SSM and Transformer architectures for a 256K context window and high efficiency.\n\nIt works with 9 languages and can handle various writing and analysis tasks as well as or better than similar small models.\n\nThis model uses less computer memory and works faster with longer texts than previous designs.\n\nRead their [announcement](https://www.ai21.com/blog/announcing-jamba-model-family) to learn more.","context_length":256000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.0000002","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":256000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/phi-3.5-mini-128k-instruct","name":"Microsoft: Phi-3.5 Mini 128K Instruct","created":1724198400,"description":"Phi-3.5 models are lightweight, state-of-the-art open models. These models were trained with Phi-3 datasets that include both synthetic data and the filtered, publicly available websites data, with a focus on high quality and reasoning-dense properties. Phi-3.5 Mini uses 3.8B parameters, and is a dense decoder-only transformer model using the same tokenizer as [Phi-3 Mini](/models/microsoft/phi-3-mini-128k-instruct).\n\nThe models underwent a rigorous enhancement process, incorporating both supervised fine-tuning, proximal policy optimization, and direct preference optimization to ensure precise instruction adherence and robust safety measures. When assessed against benchmarks that test common sense, language understanding, math, code, long context and logical reasoning, Phi-3.5 models showcased robust and state-of-the-art performance among models with less than 13 billion parameters.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"phi3"},"pricing":{"prompt":"0.0000001","completion":"0.0000001","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"nousresearch/hermes-3-llama-3.1-70b","name":"Nous: Hermes 3 70B Instruct","created":1723939200,"description":"Hermes 3 is a generalist language model with many improvements over [Hermes 2](/models/nousresearch/nous-hermes-2-mistral-7b-dpo), including advanced agentic capabilities, much better roleplaying, reasoning, multi-turn conversation, long context coherence, and improvements across the board.\n\nHermes 3 70B is a competitive, if not superior finetune of the [Llama-3.1 70B foundation model](/models/meta-llama/llama-3.1-70b-instruct), focused on aligning LLMs to the user, with powerful steering capabilities and control given to the end user.\n\nThe Hermes 3 series builds and expands on the Hermes 2 set of capabilities, including more powerful and reliable function calling and structured output capabilities, generalist assistant capabilities, and improved code generation skills.","context_length":131000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"chatml"},"pricing":{"prompt":"0.00000012","completion":"0.0000003","image":"0","request":"0"},"top_provider":{"context_length":131000,"max_completion_tokens":131000,"is_moderated":false},"per_request_limits":null},{"id":"nousresearch/hermes-3-llama-3.1-405b","name":"Nous: Hermes 3 405B Instruct","created":1723766400,"description":"Hermes 3 is a generalist language model with many improvements over Hermes 2, including advanced agentic capabilities, much better roleplaying, reasoning, multi-turn conversation, long context coherence, and improvements across the board.\n\nHermes 3 405B is a frontier-level, full-parameter finetune of the Llama-3.1 405B foundation model, focused on aligning LLMs to the user, with powerful steering capabilities and control given to the end user.\n\nThe Hermes 3 series builds and expands on the Hermes 2 set of capabilities, including more powerful and reliable function calling and structured output capabilities, generalist assistant capabilities, and improved code generation skills.\n\nHermes 3 is competitive, if not superior, to Llama-3.1 Instruct models at general capabilities, with varying strengths and weaknesses attributable between the two.","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"chatml"},"pricing":{"prompt":"0.0000008","completion":"0.0000008","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3.1-sonar-huge-128k-online","name":"Perplexity: Llama 3.1 Sonar 405B Online","created":1723593600,"description":"Llama 3.1 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance. The model is built upon the Llama 3.1 405B and has internet access.","context_length":127072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.000005","completion":"0.000005","image":"0","request":"0.005"},"top_provider":{"context_length":127072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/chatgpt-4o-latest","name":"OpenAI: ChatGPT-4o","created":1723593600,"description":"OpenAI ChatGPT 4o is continually updated by OpenAI to point to the current version of GPT-4o used by ChatGPT. It therefore differs slightly from the API version of [GPT-4o](/models/openai/gpt-4o) in that it has additional RLHF. It is intended for research and evaluation.\n\nOpenAI notes that this model is not suited for production use-cases as it may be removed or redirected to another model in the future.","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000005","completion":"0.000015","image":"0.007225","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"sao10k/l3-lunaris-8b","name":"Sao10K: Llama 3 8B Lunaris","created":1723507200,"description":"Lunaris 8B is a versatile generalist and roleplaying model based on Llama 3. It's a strategic merge of multiple models, designed to balance creativity with improved logic and general knowledge.\n\nCreated by [Sao10k](https://huggingface.co/Sao10k), this model aims to offer an improved experience over Stheno v3.2, with enhanced creativity and logical reasoning.\n\nFor best results, use with Llama 3 Instruct context template, temperature 1.4, and min_p 0.1.","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000003","completion":"0.00000006","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"aetherwiing/mn-starcannon-12b","name":"Aetherwiing: Starcannon 12B","created":1723507200,"description":"Starcannon 12B v2 is a creative roleplay and story writing model, based on Mistral Nemo, using [nothingiisreal/mn-celeste-12b](/nothingiisreal/mn-celeste-12b) as a base, with [intervitens/mini-magnum-12b-v1.1](https://huggingface.co/intervitens/mini-magnum-12b-v1.1) merged in using the [TIES](https://arxiv.org/abs/2306.01708) method.\n\nAlthough more similar to Magnum overall, the model remains very creative, with a pleasant writing style. It is recommended for people wanting more variety than Magnum, and yet more verbose prose than Celeste.","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.0000008","completion":"0.0000012","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4o-2024-08-06","name":"OpenAI: GPT-4o (2024-08-06)","created":1722902400,"description":"The 2024-08-06 version of GPT-4o offers improved performance in structured outputs, with the ability to supply a JSON schema in the respone_format. Read more [here](https://openai.com/index/introducing-structured-outputs-in-the-api/).\n\nGPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.0000025","completion":"0.00001","image":"0.003613","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"meta-llama/llama-3.1-405b","name":"Meta: Llama 3.1 405B (base)","created":1722556800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This is the base 405B pre-trained version.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"none"},"pricing":{"prompt":"0.000002","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"nothingiisreal/mn-celeste-12b","name":"Mistral Nemo 12B Celeste","created":1722556800,"description":"A specialized story writing and roleplaying model based on Mistral's NeMo 12B Instruct. Fine-tuned on curated datasets including Reddit Writing Prompts and Opus Instruct 25K.\n\nThis model excels at creative writing, offering improved NSFW capabilities, with smarter and more active narration. It demonstrates remarkable versatility in both SFW and NSFW scenarios, with strong Out of Character (OOC) steering capabilities, allowing fine-tuned control over narrative direction and character behavior.\n\nCheck out the model's [HuggingFace page](https://huggingface.co/nothingiisreal/MN-12B-Celeste-V1.9) for details on what parameters and prompts work best!","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.0000008","completion":"0.0000012","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3.1-sonar-small-128k-chat","name":"Perplexity: Llama 3.1 Sonar 8B","created":1722470400,"description":"Llama 3.1 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is a normal offline LLM, but the [online version](/models/perplexity/llama-3.1-sonar-small-128k-online) of this model has Internet access.","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.0000002","completion":"0.0000002","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-pro-1.5-exp","name":"Google: Gemini Pro 1.5 Experimental","created":1722470400,"description":"Gemini 1.5 Pro Experimental is a bleeding-edge version of the [Gemini 1.5 Pro](/models/google/gemini-pro-1.5) model. Because it's currently experimental, it will be **heavily rate-limited** by Google.\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n#multimodal","context_length":1000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":1000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3.1-sonar-large-128k-chat","name":"Perplexity: Llama 3.1 Sonar 70B","created":1722470400,"description":"Llama 3.1 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is a normal offline LLM, but the [online version](/models/perplexity/llama-3.1-sonar-large-128k-online) of this model has Internet access.","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000001","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3.1-sonar-large-128k-online","name":"Perplexity: Llama 3.1 Sonar 70B Online","created":1722470400,"description":"Llama 3.1 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is the online version of the [offline chat model](/models/perplexity/llama-3.1-sonar-large-128k-chat). It is focused on delivering helpful, up-to-date, and factual responses. #online","context_length":127072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000001","image":"0","request":"0.005"},"top_provider":{"context_length":127072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3.1-sonar-small-128k-online","name":"Perplexity: Llama 3.1 Sonar 8B Online","created":1722470400,"description":"Llama 3.1 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is the online version of the [offline chat model](/models/perplexity/llama-3.1-sonar-small-128k-chat). It is focused on delivering helpful, up-to-date, and factual responses. #online","context_length":127072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.0000002","completion":"0.0000002","image":"0","request":"0.005"},"top_provider":{"context_length":127072,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-405b-instruct:free","name":"Meta: Llama 3.1 405B Instruct (free)","created":1721692800,"description":"The highly anticipated 400B class of Llama3 is here! Clocking in at 128k context with impressive eval scores, the Meta AI team continues to push the frontier of open-source LLMs.\n\nMeta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 405B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models including GPT-4o and Claude 3.5 Sonnet in evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-405b-instruct","name":"Meta: Llama 3.1 405B Instruct","created":1721692800,"description":"The highly anticipated 400B class of Llama3 is here! Clocking in at 128k context with impressive eval scores, the Meta AI team continues to push the frontier of open-source LLMs.\n\nMeta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 405B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models including GPT-4o and Claude 3.5 Sonnet in evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000008","completion":"0.0000008","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-405b-instruct:nitro","name":"Meta: Llama 3.1 405B Instruct (nitro)","created":1721692800,"description":"The highly anticipated 400B class of Llama3 is here! Clocking in at 128k context with impressive eval scores, the Meta AI team continues to push the frontier of open-source LLMs.\n\nMeta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 405B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models including GPT-4o and Claude 3.5 Sonnet in evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00001462","completion":"0.00001462","image":"0","request":"0"},"top_provider":{"context_length":8000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-8b-instruct:free","name":"Meta: Llama 3.1 8B Instruct (free)","created":1721692800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 8B instruct-tuned version is fast and efficient.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-8b-instruct","name":"Meta: Llama 3.1 8B Instruct","created":1721692800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 8B instruct-tuned version is fast and efficient.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000002","completion":"0.00000005","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-70b-instruct:free","name":"Meta: Llama 3.1 70B Instruct (free)","created":1721692800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 70B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-70b-instruct","name":"Meta: Llama 3.1 70B Instruct","created":1721692800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 70B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":131072,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000012","completion":"0.0000003","image":"0","request":"0"},"top_provider":{"context_length":131072,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3.1-70b-instruct:nitro","name":"Meta: Llama 3.1 70B Instruct (nitro)","created":1721692800,"description":"Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 70B instruct-tuned version is optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":64000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000325","completion":"0.00000325","image":"0","request":"0"},"top_provider":{"context_length":64000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-nemo","name":"Mistral: Mistral Nemo","created":1721347200,"description":"A 12B parameter model with a 128k token context length built by Mistral in collaboration with NVIDIA.\n\nThe model is multilingual, supporting English, French, German, Spanish, Italian, Portuguese, Chinese, Japanese, Korean, Arabic, and Hindi.\n\nIt supports function calling and is released under the Apache 2.0 license.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.000000035","completion":"0.00000008","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/codestral-mamba","name":"Mistral: Codestral Mamba","created":1721347200,"description":"A 7.3B parameter Mamba-based model designed for code and reasoning tasks.\n\n- Linear time inference, allowing for theoretically infinite sequence lengths\n- 256k token context window\n- Optimized for quick responses, especially beneficial for code productivity\n- Performs comparably to state-of-the-art transformer models in code and reasoning tasks\n- Available under the Apache 2.0 license for free use, modification, and distribution","context_length":256000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.00000025","completion":"0.00000025","image":"0","request":"0"},"top_provider":{"context_length":256000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4o-mini","name":"OpenAI: GPT-4o-mini","created":1721260800,"description":"GPT-4o mini is OpenAI's newest model after [GPT-4 Omni](/models/openai/gpt-4o), supporting both text and image inputs with text outputs.\n\nAs their most advanced small model, it is many multiples more affordable than other recent frontier models, and more than 60% cheaper than [GPT-3.5 Turbo](/models/openai/gpt-3.5-turbo). It maintains SOTA intelligence, while being significantly more cost-effective.\n\nGPT-4o mini achieves an 82% score on MMLU and presently ranks higher than GPT-4 on chat preferences [common leaderboards](https://arena.lmsys.org/).\n\nCheck out the [launch announcement](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/) to learn more.\n\n#multimodal","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00000015","completion":"0.0000006","image":"0.007225","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4o-mini-2024-07-18","name":"OpenAI: GPT-4o-mini (2024-07-18)","created":1721260800,"description":"GPT-4o mini is OpenAI's newest model after [GPT-4 Omni](/models/openai/gpt-4o), supporting both text and image inputs with text outputs.\n\nAs their most advanced small model, it is many multiples more affordable than other recent frontier models, and more than 60% cheaper than [GPT-3.5 Turbo](/models/openai/gpt-3.5-turbo). It maintains SOTA intelligence, while being significantly more cost-effective.\n\nGPT-4o mini achieves an 82% score on MMLU and presently ranks higher than GPT-4 on chat preferences [common leaderboards](https://arena.lmsys.org/).\n\nCheck out the [launch announcement](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/) to learn more.\n\n#multimodal","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00000015","completion":"0.0000006","image":"0.007225","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"qwen/qwen-2-7b-instruct:free","name":"Qwen 2 7B Instruct (free)","created":1721088000,"description":"Qwen2 7B is a transformer-based model that excels in language understanding, multilingual capabilities, coding, mathematics, and reasoning.\n\nIt features SwiGLU activation, attention QKV bias, and group query attention. It is pretrained on extensive data with supervised finetuning and direct preference optimization.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2/) and [GitHub repo](https://github.com/QwenLM/Qwen2).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2-7b-instruct","name":"Qwen 2 7B Instruct","created":1721088000,"description":"Qwen2 7B is a transformer-based model that excels in language understanding, multilingual capabilities, coding, mathematics, and reasoning.\n\nIt features SwiGLU activation, attention QKV bias, and group query attention. It is pretrained on extensive data with supervised finetuning and direct preference optimization.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2/) and [GitHub repo](https://github.com/QwenLM/Qwen2).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.000000054","completion":"0.000000054","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemma-2-27b-it","name":"Google: Gemma 2 27B","created":1720828800,"description":"Gemma 2 27B by Google is an open model built from the same research and technology used to create the [Gemini models](/models?q=gemini).\n\nGemma models are well-suited for a variety of text generation tasks, including question answering, summarization, and reasoning.\n\nSee the [launch announcement](https://blog.google/technology/developers/google-gemma-2/) for more details. Usage of Gemma is subject to Google's [Gemma Terms of Use](https://ai.google.dev/gemma/terms).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Gemini","instruct_type":"gemma"},"pricing":{"prompt":"0.00000027","completion":"0.00000027","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"alpindale/magnum-72b","name":"Magnum 72B","created":1720656000,"description":"From the maker of [Goliath](https://openrouter.ai/models/alpindale/goliath-120b), Magnum 72B is the first in a new family of models designed to achieve the prose quality of the Claude 3 models, notably Opus & Sonnet.\n\nThe model is based on [Qwen2 72B](https://openrouter.ai/models/qwen/qwen-2-72b-instruct) and trained with 55 million tokens of highly curated roleplay (RP) data.","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.000001875","completion":"0.00000225","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":1024,"is_moderated":false},"per_request_limits":null},{"id":"google/gemma-2-9b-it:free","name":"Google: Gemma 2 9B (free)","created":1719532800,"description":"Gemma 2 9B by Google is an advanced, open-source language model that sets a new standard for efficiency and performance in its size class.\n\nDesigned for a wide variety of tasks, it empowers developers and researchers to build innovative applications, while maintaining accessibility, safety, and cost-effectiveness.\n\nSee the [launch announcement](https://blog.google/technology/developers/google-gemma-2/) for more details. Usage of Gemma is subject to Google's [Gemma Terms of Use](https://ai.google.dev/gemma/terms).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Gemini","instruct_type":"gemma"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"google/gemma-2-9b-it","name":"Google: Gemma 2 9B","created":1719532800,"description":"Gemma 2 9B by Google is an advanced, open-source language model that sets a new standard for efficiency and performance in its size class.\n\nDesigned for a wide variety of tasks, it empowers developers and researchers to build innovative applications, while maintaining accessibility, safety, and cost-effectiveness.\n\nSee the [launch announcement](https://blog.google/technology/developers/google-gemma-2/) for more details. Usage of Gemma is subject to Google's [Gemma Terms of Use](https://ai.google.dev/gemma/terms).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Gemini","instruct_type":"gemma"},"pricing":{"prompt":"0.00000003","completion":"0.00000006","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"01-ai/yi-large","name":"01.AI: Yi Large","created":1719273600,"description":"The Yi Large model was designed by 01.AI with the following usecases in mind: knowledge search, data classification, human-like chat bots, and customer service.\n\nIt stands out for its multilingual proficiency, particularly in Spanish, Chinese, Japanese, German, and French.\n\nCheck out the [launch announcement](https://01-ai.github.io/blog/01.ai-yi-large-llm-launch) to learn more.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Yi","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000003","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"ai21/jamba-instruct","name":"AI21: Jamba Instruct","created":1719273600,"description":"The Jamba-Instruct model, introduced by AI21 Labs, is an instruction-tuned variant of their hybrid SSM-Transformer Jamba model, specifically optimized for enterprise applications.\n\n- 256K Context Window: It can process extensive information, equivalent to a 400-page novel, which is beneficial for tasks involving large documents such as financial reports or legal documents\n- Safety and Accuracy: Jamba-Instruct is designed with enhanced safety features to ensure secure deployment in enterprise environments, reducing the risk and cost of implementation\n\nRead their [announcement](https://www.ai21.com/blog/announcing-jamba) to learn more.\n\nJamba has a knowledge cutoff of February 2024.","context_length":256000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.0000005","completion":"0.0000007","image":"0","request":"0"},"top_provider":{"context_length":256000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-sonnet-20240620:beta","name":"Anthropic: Claude 3.5 Sonnet (2024-06-20) (self-moderated)","created":1718841600,"description":"Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Autonomously writes, edits, and runs code with reasoning and troubleshooting\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\nFor the latest version (2024-10-23), check out [Claude 3.5 Sonnet](/anthropic/claude-3.5-sonnet).\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3.5-sonnet-20240620","name":"Anthropic: Claude 3.5 Sonnet (2024-06-20)","created":1718841600,"description":"Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Autonomously writes, edits, and runs code with reasoning and troubleshooting\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\nFor the latest version (2024-10-23), check out [Claude 3.5 Sonnet](/anthropic/claude-3.5-sonnet).\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":8192,"is_moderated":true},"per_request_limits":null},{"id":"sao10k/l3-euryale-70b","name":"Sao10k: Llama 3 Euryale 70B v2.1","created":1718668800,"description":"Euryale 70B v2.1 is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k).\n\n- Better prompt adherence.\n- Better anatomy / spatial awareness.\n- Adapts much better to unique and custom formatting / reply formats.\n- Very creative, lots of unique swipes.\n- Is not restrictive during roleplays.","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000035","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"cognitivecomputations/dolphin-mixtral-8x22b","name":"Dolphin 2.9.2 Mixtral 8x22B 🐬","created":1717804800,"description":"Dolphin 2.9 is designed for instruction following, conversational, and coding. This model is a finetune of [Mixtral 8x22B Instruct](/models/mistralai/mixtral-8x22b-instruct). It features a 64k context length and was fine-tuned with a 16k sequence length using ChatML templates.\n\nThis model is a successor to [Dolphin Mixtral 8x7B](/models/cognitivecomputations/dolphin-mixtral-8x7b).\n\nThe model is uncensored and is stripped of alignment and bias. It requires an external alignment layer for ethical use. Users are cautioned to use this highly compliant model responsibly, as detailed in a blog post about uncensored models at [erichartford.com/uncensored-models](https://erichartford.com/uncensored-models).\n\n#moe #uncensored","context_length":16000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.0000009","completion":"0.0000009","image":"0","request":"0"},"top_provider":{"context_length":16000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"qwen/qwen-2-72b-instruct","name":"Qwen 2 72B Instruct","created":1717718400,"description":"Qwen2 72B is a transformer-based model that excels in language understanding, multilingual capabilities, coding, mathematics, and reasoning.\n\nIt features SwiGLU activation, attention QKV bias, and group query attention. It is pretrained on extensive data with supervised finetuning and direct preference optimization.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2/) and [GitHub repo](https://github.com/QwenLM/Qwen2).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Qwen","instruct_type":"chatml"},"pricing":{"prompt":"0.00000034","completion":"0.00000039","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct:free","name":"Mistral: Mistral 7B Instruct (free)","created":1716768000,"description":"A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\n*Mistral 7B Instruct has multiple version variants, and this is intended to be the latest version.*","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct","name":"Mistral: Mistral 7B Instruct","created":1716768000,"description":"A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\n*Mistral 7B Instruct has multiple version variants, and this is intended to be the latest version.*","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000003","completion":"0.000000055","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct:nitro","name":"Mistral: Mistral 7B Instruct (nitro)","created":1716768000,"description":"A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\n*Mistral 7B Instruct has multiple version variants, and this is intended to be the latest version.*","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000007","completion":"0.00000007","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct-v0.3","name":"Mistral: Mistral 7B Instruct v0.3","created":1716768000,"description":"A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\nAn improved version of [Mistral 7B Instruct v0.2](/models/mistralai/mistral-7b-instruct-v0.2), with the following changes:\n\n- Extended vocabulary to 32768\n- Supports v3 Tokenizer\n- Supports function calling\n\nNOTE: Support for function calling depends on the provider.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000003","completion":"0.000000055","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"nousresearch/hermes-2-pro-llama-3-8b","name":"NousResearch: Hermes 2 Pro - Llama-3 8B","created":1716768000,"description":"Hermes 2 Pro is an upgraded, retrained version of Nous Hermes 2, consisting of an updated and cleaned version of the OpenHermes 2.5 Dataset, as well as a newly introduced Function Calling and JSON Mode dataset developed in-house.","context_length":131000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"chatml"},"pricing":{"prompt":"0.000000025","completion":"0.00000004","image":"0","request":"0"},"top_provider":{"context_length":131000,"max_completion_tokens":131000,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/phi-3-mini-128k-instruct:free","name":"Microsoft: Phi-3 Mini 128K Instruct (free)","created":1716681600,"description":"Phi-3 Mini is a powerful 3.8B parameter model designed for advanced language understanding, reasoning, and instruction following. Optimized through supervised fine-tuning and preference adjustments, it excels in tasks involving common sense, mathematics, logical reasoning, and code processing.\n\nAt time of release, Phi-3 Medium demonstrated state-of-the-art performance among lightweight models. This model is static, trained on an offline dataset with an October 2023 cutoff date.","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"phi3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/phi-3-mini-128k-instruct","name":"Microsoft: Phi-3 Mini 128K Instruct","created":1716681600,"description":"Phi-3 Mini is a powerful 3.8B parameter model designed for advanced language understanding, reasoning, and instruction following. Optimized through supervised fine-tuning and preference adjustments, it excels in tasks involving common sense, mathematics, logical reasoning, and code processing.\n\nAt time of release, Phi-3 Medium demonstrated state-of-the-art performance among lightweight models. This model is static, trained on an offline dataset with an October 2023 cutoff date.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"phi3"},"pricing":{"prompt":"0.0000001","completion":"0.0000001","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/phi-3-medium-128k-instruct:free","name":"Microsoft: Phi-3 Medium 128K Instruct (free)","created":1716508800,"description":"Phi-3 128K Medium is a powerful 14-billion parameter model designed for advanced language understanding, reasoning, and instruction following. Optimized through supervised fine-tuning and preference adjustments, it excels in tasks involving common sense, mathematics, logical reasoning, and code processing.\n\nAt time of release, Phi-3 Medium demonstrated state-of-the-art performance among lightweight models. In the MMLU-Pro eval, the model even comes close to a Llama3 70B level of performance.\n\nFor 4k context length, try [Phi-3 Medium 4K](/models/microsoft/phi-3-medium-4k-instruct).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"phi3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/phi-3-medium-128k-instruct","name":"Microsoft: Phi-3 Medium 128K Instruct","created":1716508800,"description":"Phi-3 128K Medium is a powerful 14-billion parameter model designed for advanced language understanding, reasoning, and instruction following. Optimized through supervised fine-tuning and preference adjustments, it excels in tasks involving common sense, mathematics, logical reasoning, and code processing.\n\nAt time of release, Phi-3 Medium demonstrated state-of-the-art performance among lightweight models. In the MMLU-Pro eval, the model even comes close to a Llama3 70B level of performance.\n\nFor 4k context length, try [Phi-3 Medium 4K](/models/microsoft/phi-3-medium-4k-instruct).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"phi3"},"pricing":{"prompt":"0.000001","completion":"0.000001","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"neversleep/llama-3-lumimaid-70b","name":"NeverSleep: Llama 3 Lumimaid 70B","created":1715817600,"description":"The NeverSleep team is back, with a Llama 3 70B finetune trained on their curated roleplay data. Striking a balance between eRP and RP, Lumimaid was designed to be serious, yet uncensored when necessary.\n\nTo enhance it's overall intelligence and chat capability, roughly 40% of the training data was not roleplay. This provides a breadth of knowledge to access, while still keeping roleplay as the primary strength.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000003375","completion":"0.0000045","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-flash-1.5","name":"Google: Gemini Flash 1.5","created":1715644800,"description":"Gemini 1.5 Flash is a foundation model that performs well at a variety of multimodal tasks such as visual understanding, classification, summarization, and creating content from image, audio and video. It's adept at processing visual and text inputs such as photographs, documents, infographics, and screenshots.\n\nGemini 1.5 Flash is designed for high-volume, high-frequency tasks where cost and latency matter. On most common tasks, Flash achieves comparable quality to other Gemini Pro models at a significantly reduced cost. Flash is well-suited for applications like chat assistants and on-demand content generation where speed and scale matter.\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n#multimodal","context_length":1000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0.000000075","completion":"0.0000003","image":"0.00004","request":"0"},"top_provider":{"context_length":1000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3-sonar-large-32k-chat","name":"Perplexity: Llama3 Sonar 70B","created":1715644800,"description":"Llama3 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is a normal offline LLM, but the [online version](/models/perplexity/llama-3-sonar-large-32k-online) of this model has Internet access.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000001","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3-sonar-large-32k-online","name":"Perplexity: Llama3 Sonar 70B Online","created":1715644800,"description":"Llama3 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is the online version of the [offline chat model](/models/perplexity/llama-3-sonar-large-32k-chat). It is focused on delivering helpful, up-to-date, and factual responses. #online","context_length":28000,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000001","image":"0","request":"0.005"},"top_provider":{"context_length":28000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"deepseek/deepseek-chat-v2.5","name":"DeepSeek V2.5","created":1715644800,"description":"DeepSeek-V2.5 is an upgraded version that combines DeepSeek-V2-Chat and DeepSeek-Coder-V2-Instruct. The new model integrates the general and coding abilities of the two previous versions. For model details, please visit [DeepSeek-V2 page](https://github.com/deepseek-ai/DeepSeek-V2) for more information.","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"perplexity/llama-3-sonar-small-32k-chat","name":"Perplexity: Llama3 Sonar 8B","created":1715644800,"description":"Llama3 Sonar is Perplexity's latest model family. It surpasses their earlier Sonar models in cost-efficiency, speed, and performance.\n\nThis is a normal offline LLM, but the [online version](/models/perplexity/llama-3-sonar-small-32k-online) of this model has Internet access.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":null},"pricing":{"prompt":"0.0000002","completion":"0.0000002","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4o-2024-05-13","name":"OpenAI: GPT-4o (2024-05-13)","created":1715558400,"description":"GPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)\n\n#multimodal","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000005","completion":"0.000015","image":"0.007225","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"meta-llama/llama-guard-2-8b","name":"Meta: LlamaGuard 2 8B","created":1715558400,"description":"This safeguard model has 8B parameters and is based on the Llama 3 family. Just like is predecessor, [LlamaGuard 1](https://huggingface.co/meta-llama/LlamaGuard-7b), it can do both prompt and response classification.\n\nLlamaGuard 2 acts as a normal LLM would, generating text that indicates whether the given input/output is safe/unsafe. If deemed unsafe, it will also share the content categories violated.\n\nFor best results, please use raw prompt input or the `/completions` endpoint, instead of the chat API.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"none"},"pricing":{"prompt":"0.00000018","completion":"0.00000018","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4o","name":"OpenAI: GPT-4o","created":1715558400,"description":"GPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)\n\n#multimodal","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.0000025","completion":"0.00001","image":"0.003613","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":16384,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4o:extended","name":"OpenAI: GPT-4o (extended)","created":1715558400,"description":"GPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)\n\n#multimodal","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000006","completion":"0.000018","image":"0.007225","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":64000,"is_moderated":true},"per_request_limits":null},{"id":"neversleep/llama-3-lumimaid-8b:extended","name":"NeverSleep: Llama 3 Lumimaid 8B (extended)","created":1714780800,"description":"The NeverSleep team is back, with a Llama 3 8B finetune trained on their curated roleplay data. Striking a balance between eRP and RP, Lumimaid was designed to be serious, yet uncensored when necessary.\n\nTo enhance it's overall intelligence and chat capability, roughly 40% of the training data was not roleplay. This provides a breadth of knowledge to access, while still keeping roleplay as the primary strength.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":24576,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000001875","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":24576,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"neversleep/llama-3-lumimaid-8b","name":"NeverSleep: Llama 3 Lumimaid 8B","created":1714780800,"description":"The NeverSleep team is back, with a Llama 3 8B finetune trained on their curated roleplay data. Striking a balance between eRP and RP, Lumimaid was designed to be serious, yet uncensored when necessary.\n\nTo enhance it's overall intelligence and chat capability, roughly 40% of the training data was not roleplay. This provides a breadth of knowledge to access, while still keeping roleplay as the primary strength.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":24576,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000001875","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":24576,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-8b-instruct:free","name":"Meta: Llama 3 8B Instruct (free)","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 8B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-8b-instruct","name":"Meta: Llama 3 8B Instruct","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 8B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000003","completion":"0.00000006","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-8b-instruct:extended","name":"Meta: Llama 3 8B Instruct (extended)","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 8B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":16384,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000001875","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-8b-instruct:nitro","name":"Meta: Llama 3 8B Instruct (nitro)","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 8B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.0000002","completion":"0.0000002","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-70b-instruct","name":"Meta: Llama 3 70B Instruct","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 70B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.00000023","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-3-70b-instruct:nitro","name":"Meta: Llama 3 70B Instruct (nitro)","created":1713398400,"description":"Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 70B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama3","instruct_type":"llama3"},"pricing":{"prompt":"0.000000792","completion":"0.000000792","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mixtral-8x22b-instruct","name":"Mistral: Mixtral 8x22B Instruct","created":1713312000,"description":"Mistral's official instruct fine-tuned version of [Mixtral 8x22B](/models/mistralai/mixtral-8x22b). It uses 39B active parameters out of 141B, offering unparalleled cost efficiency for its size. Its strengths include:\n- strong math, coding, and reasoning\n- large context length (64k)\n- fluency in English, French, Italian, German, and Spanish\n\nSee benchmarks on the launch announcement [here](https://mistral.ai/news/mixtral-8x22b/).\n#moe","context_length":65536,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.0000009","completion":"0.0000009","image":"0","request":"0"},"top_provider":{"context_length":65536,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/wizardlm-2-8x22b","name":"WizardLM-2 8x22B","created":1713225600,"description":"WizardLM-2 8x22B is Microsoft AI's most advanced Wizard model. It demonstrates highly competitive performance compared to leading proprietary models, and it consistently outperforms all existing state-of-the-art opensource models.\n\nIt is an instruct finetune of [Mixtral 8x22B](/models/mistralai/mixtral-8x22b).\n\nTo read more about the model release, [click here](https://wizardlm.github.io/WizardLM2/).\n\n#moe","context_length":65536,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"vicuna"},"pricing":{"prompt":"0.0000005","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":65536,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"microsoft/wizardlm-2-7b","name":"WizardLM-2 7B","created":1713225600,"description":"WizardLM-2 7B is the smaller variant of Microsoft AI's latest Wizard model. It is the fastest and achieves comparable performance with existing 10x larger opensource leading models\n\nIt is a finetune of [Mistral 7B Instruct](/models/mistralai/mistral-7b-instruct), using the same technique as [WizardLM-2 8x22B](/models/microsoft/wizardlm-2-8x22b).\n\nTo read more about the model release, [click here](https://wizardlm.github.io/WizardLM2/).\n\n#moe","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"vicuna"},"pricing":{"prompt":"0.000000055","completion":"0.000000055","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-pro-1.5","name":"Google: Gemini Pro 1.5","created":1712620800,"description":"Google's latest multimodal model, supports image and video[0] in text or chat prompts.\n\nOptimized for language tasks including:\n\n- Code generation\n- Text generation\n- Text editing\n- Problem solving\n- Recommendations\n- Information extraction\n- Data extraction or generation\n- AI agents\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n* [0]: Video input is not available through OpenRouter at this time.","context_length":2000000,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0.00000125","completion":"0.000005","image":"0.0006575","request":"0"},"top_provider":{"context_length":2000000,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4-turbo","name":"OpenAI: GPT-4 Turbo","created":1712620800,"description":"The latest GPT-4 Turbo model with vision capabilities. Vision requests can now use JSON mode and function calling.\n\nTraining data: up to December 2023.","context_length":128000,"architecture":{"modality":"text+image->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00001","completion":"0.00003","image":"0.01445","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"cohere/command-r-plus","name":"Cohere: Command R+","created":1712188800,"description":"Command R+ is a new, 104B-parameter LLM from Cohere. It's useful for roleplay, general consumer usecases, and Retrieval Augmented Generation (RAG).\n\nIt offers multilingual support for ten key languages to facilitate global business operations. See benchmarks and the launch post [here](https://txt.cohere.com/command-r-plus-microsoft-azure/).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.00000285","completion":"0.00001425","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command-r-plus-04-2024","name":"Cohere: Command R+ (04-2024)","created":1712016000,"description":"Command R+ is a new, 104B-parameter LLM from Cohere. It's useful for roleplay, general consumer usecases, and Retrieval Augmented Generation (RAG).\n\nIt offers multilingual support for ten key languages to facilitate global business operations. See benchmarks and the launch post [here](https://txt.cohere.com/command-r-plus-microsoft-azure/).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.00000285","completion":"0.00001425","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"databricks/dbrx-instruct","name":"Databricks: DBRX 132B Instruct","created":1711670400,"description":"DBRX is a new open source large language model developed by Databricks. At 132B, it outperforms existing open source LLMs like Llama 2 70B and [Mixtral-8x7b](/models/mistralai/mixtral-8x7b) on standard industry benchmarks for language understanding, programming, math, and logic.\n\nIt uses a fine-grained mixture-of-experts (MoE) architecture. 36B parameters are active on any input. It was pre-trained on 12T tokens of text and code data. Compared to other open MoE models like Mixtral-8x7B and Grok-1, DBRX is fine-grained, meaning it uses a larger number of smaller experts.\n\nSee the launch announcement and benchmark results [here](https://www.databricks.com/blog/introducing-dbrx-new-state-art-open-llm).\n\n#moe","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Other","instruct_type":"chatml"},"pricing":{"prompt":"0.00000108","completion":"0.00000108","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"sophosympatheia/midnight-rose-70b","name":"Midnight Rose 70B","created":1711065600,"description":"A merge with a complex family tree, this model was crafted for roleplaying and storytelling. Midnight Rose is a successor to Rogue Rose and Aurora Nights and improves upon them both. It wants to produce lengthy output by default and is the best creative writing merge produced so far by sophosympatheia.\n\nDescending from earlier versions of Midnight Rose and [Wizard Tulu Dolphin 70B](https://huggingface.co/sophosympatheia/Wizard-Tulu-Dolphin-70B-v1.0), it inherits the best qualities of each.","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"airoboros"},"pricing":{"prompt":"0.0000008","completion":"0.0000008","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command","name":"Cohere: Command","created":1710374400,"description":"Command is an instruction-following conversational model that performs language tasks with high quality, more reliably and with a longer context than our base generative models.\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.00000095","completion":"0.0000019","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"cohere/command-r","name":"Cohere: Command R","created":1710374400,"description":"Command-R is a 35B parameter model that performs conversational language tasks at a higher quality, more reliably, and with a longer context than previous models. It can be used for complex workflows like code generation, retrieval augmented generation (RAG), tool use, and agents.\n\nRead the launch post [here](https://txt.cohere.com/command-r/).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.000000475","completion":"0.000001425","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3-haiku:beta","name":"Anthropic: Claude 3 Haiku (self-moderated)","created":1710288000,"description":"Claude 3 Haiku is Anthropic's fastest and most compact model for\nnear-instant responsiveness. Quick and accurate targeted performance.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-haiku)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.00000025","completion":"0.00000125","image":"0.0004","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3-haiku","name":"Anthropic: Claude 3 Haiku","created":1710288000,"description":"Claude 3 Haiku is Anthropic's fastest and most compact model for\nnear-instant responsiveness. Quick and accurate targeted performance.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-haiku)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.00000025","completion":"0.00000125","image":"0.0004","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"anthropic/claude-3-opus:beta","name":"Anthropic: Claude 3 Opus (self-moderated)","created":1709596800,"description":"Claude 3 Opus is Anthropic's most powerful model for highly complex tasks. It boasts top-level performance, intelligence, fluency, and understanding.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-family)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000015","completion":"0.000075","image":"0.024","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3-opus","name":"Anthropic: Claude 3 Opus","created":1709596800,"description":"Claude 3 Opus is Anthropic's most powerful model for highly complex tasks. It boasts top-level performance, intelligence, fluency, and understanding.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-family)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000015","completion":"0.000075","image":"0.024","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"anthropic/claude-3-sonnet:beta","name":"Anthropic: Claude 3 Sonnet (self-moderated)","created":1709596800,"description":"Claude 3 Sonnet is an ideal balance of intelligence and speed for enterprise workloads. Maximum utility at a lower price, dependable, balanced for scaled deployments.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-family)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-3-sonnet","name":"Anthropic: Claude 3 Sonnet","created":1709596800,"description":"Claude 3 Sonnet is an ideal balance of intelligence and speed for enterprise workloads. Maximum utility at a lower price, dependable, balanced for scaled deployments.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-family)\n\n#multimodal","context_length":200000,"architecture":{"modality":"text+image->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000015","image":"0.0048","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"cohere/command-r-03-2024","name":"Cohere: Command R (03-2024)","created":1709341200,"description":"Command-R is a 35B parameter model that performs conversational language tasks at a higher quality, more reliably, and with a longer context than previous models. It can be used for complex workflows like code generation, retrieval augmented generation (RAG), tool use, and agents.\n\nRead the launch post [here](https://txt.cohere.com/command-r/).\n\nUse of this model is subject to Cohere's [Acceptable Use Policy](https://docs.cohere.com/docs/c4ai-acceptable-use-policy).","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Cohere","instruct_type":null},"pricing":{"prompt":"0.000000475","completion":"0.000001425","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4000,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-large","name":"Mistral Large","created":1708905600,"description":"This is Mistral AI's flagship model, Mistral Large 2 (version `mistral-large-2407`). It's a proprietary weights-available model and excels at reasoning, code, JSON, chat, and more. Read the launch announcement [here](https://mistral.ai/news/mistral-large-2407/).\n\nIt supports dozens of languages including French, German, Spanish, Italian, Portuguese, Arabic, Hindi, Russian, Chinese, Japanese, and Korean, along with 80+ coding languages including Python, Java, C, C++, JavaScript, and Bash. Its long context window allows precise information recall from large documents.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.000002","completion":"0.000006","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo-0613","name":"OpenAI: GPT-3.5 Turbo (older v0613)","created":1706140800,"description":"GPT-3.5 Turbo is OpenAI's fastest model. It can understand and generate natural language or code, and is optimized for chat and traditional completion tasks.\n\nTraining data up to Sep 2021.","context_length":4095,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":4095,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-4-turbo-preview","name":"OpenAI: GPT-4 Turbo Preview","created":1706140800,"description":"The preview GPT-4 model with improved instruction following, JSON mode, reproducible outputs, parallel function calling, and more. Training data: up to Dec 2023.\n\n**Note:** heavily rate limited by OpenAI while in preview.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00001","completion":"0.00003","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"nousresearch/nous-hermes-2-mixtral-8x7b-dpo","name":"Nous: Hermes 2 Mixtral 8x7B DPO","created":1705363200,"description":"Nous Hermes 2 Mixtral 8x7B DPO is the new flagship Nous Research model trained over the [Mixtral 8x7B MoE LLM](/models/mistralai/mixtral-8x7b).\n\nThe model was trained on over 1,000,000 entries of primarily [GPT-4](/models/openai/gpt-4) generated data, as well as other high quality data from open datasets across the AI landscape, achieving state of the art performance on a variety of tasks.\n\n#moe","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.00000054","completion":"0.00000054","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-small","name":"Mistral Small","created":1704844800,"description":"With 22 billion parameters, Mistral Small v24.09 offers a convenient mid-point between (Mistral NeMo 12B)[/mistralai/mistral-nemo] and (Mistral Large 2)[/mistralai/mistral-large], providing a cost-effective solution that can be deployed across various platforms and environments. It has better reasoning, exhibits more capabilities, can produce and reason about code, and is multiligual, supporting English, French, German, Italian, and Spanish.","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.0000002","completion":"0.0000006","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-tiny","name":"Mistral Tiny","created":1704844800,"description":"This model is currently powered by Mistral-7B-v0.2, and incorporates a \"better\" fine-tuning than [Mistral 7B](/models/mistralai/mistral-7b-instruct-v0.1), inspired by community work. It's best used for large batch processing tasks where cost is a significant factor but reasoning capabilities are not crucial.","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.00000025","completion":"0.00000025","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-medium","name":"Mistral Medium","created":1704844800,"description":"This is Mistral AI's closed-source, medium-sided model. It's powered by a closed-source prototype and excels at reasoning, code, JSON, chat, and more. In benchmarks, it compares with many of the flagship models of other companies.","context_length":32000,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":null},"pricing":{"prompt":"0.00000275","completion":"0.0000081","image":"0","request":"0"},"top_provider":{"context_length":32000,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct-v0.2","name":"Mistral: Mistral 7B Instruct v0.2","created":1703721600,"description":"A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\nAn improved version of [Mistral 7B Instruct](/modelsmistralai/mistral-7b-instruct-v0.1), with the following changes:\n\n- 32k context window (vs 8k context in v0.1)\n- Rope-theta = 1e6\n- No Sliding-Window Attention","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000018","completion":"0.00000018","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"cognitivecomputations/dolphin-mixtral-8x7b","name":"Dolphin 2.6 Mixtral 8x7B 🐬","created":1703116800,"description":"This is a 16k context fine-tune of [Mixtral-8x7b](/models/mistralai/mixtral-8x7b). It excels in coding tasks due to extensive training with coding data and is known for its obedience, although it lacks DPO tuning.\n\nThe model is uncensored and is stripped of alignment and bias. It requires an external alignment layer for ethical use. Users are cautioned to use this highly compliant model responsibly, as detailed in a blog post about uncensored models at [erichartford.com/uncensored-models](https://erichartford.com/uncensored-models).\n\n#moe #uncensored","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.0000005","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-pro-vision","name":"Google: Gemini Pro Vision 1.0","created":1702425600,"description":"Google's flagship multimodal model, supporting image and video in text or chat prompts for a text or code response.\n\nSee the benchmarks and prompting guidelines from [Deepmind](https://deepmind.google/technologies/gemini/).\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).\n\n#multimodal","context_length":16384,"architecture":{"modality":"text+image->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0.0000005","completion":"0.0000015","image":"0.0025","request":"0"},"top_provider":{"context_length":16384,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"google/gemini-pro","name":"Google: Gemini Pro 1.0","created":1702425600,"description":"Google's flagship text generation model. Designed to handle natural language tasks, multiturn text and code chat, and code generation.\n\nSee the benchmarks and prompting guidelines from [Deepmind](https://deepmind.google/technologies/gemini/).\n\nUsage of Gemini is subject to Google's [Gemini Terms of Use](https://ai.google.dev/terms).","context_length":32760,"architecture":{"modality":"text->text","tokenizer":"Gemini","instruct_type":null},"pricing":{"prompt":"0.0000005","completion":"0.0000015","image":"0.0025","request":"0"},"top_provider":{"context_length":32760,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mixtral-8x7b","name":"Mistral: Mixtral 8x7B (base)","created":1702166400,"description":"Mixtral 8x7B is a pretrained generative Sparse Mixture of Experts, by Mistral AI. Incorporates 8 experts (feed-forward networks) for a total of 47B parameters. Base model (not fine-tuned for instructions) - see [Mixtral 8x7B Instruct](/models/mistralai/mixtral-8x7b-instruct) for an instruct-tuned model.\n\n#moe","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"none"},"pricing":{"prompt":"0.00000054","completion":"0.00000054","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mixtral-8x7b-instruct","name":"Mistral: Mixtral 8x7B Instruct","created":1702166400,"description":"Mixtral 8x7B Instruct is a pretrained generative Sparse Mixture of Experts, by Mistral AI, for chat and instruction use. Incorporates 8 experts (feed-forward networks) for a total of 47 billion parameters.\n\nInstruct model fine-tuned by Mistral. #moe","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000024","completion":"0.00000024","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"mistralai/mixtral-8x7b-instruct:nitro","name":"Mistral: Mixtral 8x7B Instruct (nitro)","created":1702166400,"description":"Mixtral 8x7B Instruct is a pretrained generative Sparse Mixture of Experts, by Mistral AI, for chat and instruction use. Incorporates 8 experts (feed-forward networks) for a total of 47 billion parameters.\n\nInstruct model fine-tuned by Mistral. #moe","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000054","completion":"0.00000054","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openchat/openchat-7b:free","name":"OpenChat 3.5 7B (free)","created":1701129600,"description":"OpenChat 7B is a library of open-source language models, fine-tuned with \"C-RLFT (Conditioned Reinforcement Learning Fine-Tuning)\" - a strategy inspired by offline reinforcement learning. It has been trained on mixed-quality data without preference labels.\n\n- For OpenChat fine-tuned on Mistral 7B, check out [OpenChat 7B](/models/openchat/openchat-7b).\n- For OpenChat fine-tuned on Llama 8B, check out [OpenChat 8B](/models/openchat/openchat-8b).\n\n#open-source","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"openchat"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"openchat/openchat-7b","name":"OpenChat 3.5 7B","created":1701129600,"description":"OpenChat 7B is a library of open-source language models, fine-tuned with \"C-RLFT (Conditioned Reinforcement Learning Fine-Tuning)\" - a strategy inspired by offline reinforcement learning. It has been trained on mixed-quality data without preference labels.\n\n- For OpenChat fine-tuned on Mistral 7B, check out [OpenChat 7B](/models/openchat/openchat-7b).\n- For OpenChat fine-tuned on Llama 8B, check out [OpenChat 8B](/models/openchat/openchat-8b).\n\n#open-source","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"openchat"},"pricing":{"prompt":"0.000000055","completion":"0.000000055","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"neversleep/noromaid-20b","name":"Noromaid 20B","created":1700956800,"description":"A collab between IkariDev and Undi. This merge is suitable for RP, ERP, and general knowledge.\n\n#merge #uncensored","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.0000015","completion":"0.00000225","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-2:beta","name":"Anthropic: Claude v2 (self-moderated)","created":1700611200,"description":"Claude 2 delivers advancements in key capabilities for enterprises—including an industry-leading 200K token context window, significant reductions in rates of model hallucination, system prompts and a new beta feature: tool use.","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-2","name":"Anthropic: Claude v2","created":1700611200,"description":"Claude 2 delivers advancements in key capabilities for enterprises—including an industry-leading 200K token context window, significant reductions in rates of model hallucination, system prompts and a new beta feature: tool use.","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"anthropic/claude-2.1:beta","name":"Anthropic: Claude v2.1 (self-moderated)","created":1700611200,"description":"Claude 2 delivers advancements in key capabilities for enterprises—including an industry-leading 200K token context window, significant reductions in rates of model hallucination, system prompts and a new beta feature: tool use.","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-2.1","name":"Anthropic: Claude v2.1","created":1700611200,"description":"Claude 2 delivers advancements in key capabilities for enterprises—including an industry-leading 200K token context window, significant reductions in rates of model hallucination, system prompts and a new beta feature: tool use.","context_length":200000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":200000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"teknium/openhermes-2.5-mistral-7b","name":"OpenHermes 2.5 Mistral 7B","created":1700438400,"description":"A continuation of [OpenHermes 2 model](/models/teknium/openhermes-2-mistral-7b), trained on additional code datasets.\nPotentially the most interesting finding from training on a good ratio (est. of around 7-14% of the total dataset) of code instruction was that it has boosted several non-code benchmarks, including TruthfulQA, AGIEval, and GPT4All suite. It did however reduce BigBench benchmark score, but the net gain overall is significant.","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"chatml"},"pricing":{"prompt":"0.00000017","completion":"0.00000017","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"lizpreciatior/lzlv-70b-fp16-hf","name":"lzlv 70B","created":1699747200,"description":"A Mythomax/MLewd_13B-style merge of selected 70B models.\nA multi-model merge of several LLaMA2 70B finetunes for roleplaying and creative work. The goal was to create a model that combines creativity with intelligence for an enhanced experience.\n\n#merge #uncensored","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"airoboros"},"pricing":{"prompt":"0.00000035","completion":"0.0000004","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"undi95/toppy-m-7b:free","name":"Toppy M 7B (free)","created":1699574400,"description":"A wild 7B parameter model that merges several models using the new task_arithmetic merge method from mergekit.\nList of merged models:\n- NousResearch/Nous-Capybara-7B-V1.9\n- [HuggingFaceH4/zephyr-7b-beta](/models/huggingfaceh4/zephyr-7b-beta)\n- lemonilia/AshhLimaRP-Mistral-7B\n- Vulkane/120-Days-of-Sodom-LoRA-Mistral-7b\n- Undi95/Mistral-pippa-sharegpt-7b-qlora\n\n#merge #uncensored","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"alpaca"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"undi95/toppy-m-7b:nitro","name":"Toppy M 7B (nitro)","created":1699574400,"description":"A wild 7B parameter model that merges several models using the new task_arithmetic merge method from mergekit.\nList of merged models:\n- NousResearch/Nous-Capybara-7B-V1.9\n- [HuggingFaceH4/zephyr-7b-beta](/models/huggingfaceh4/zephyr-7b-beta)\n- lemonilia/AshhLimaRP-Mistral-7B\n- Vulkane/120-Days-of-Sodom-LoRA-Mistral-7b\n- Undi95/Mistral-pippa-sharegpt-7b-qlora\n\n#merge #uncensored","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"alpaca"},"pricing":{"prompt":"0.00000007","completion":"0.00000007","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"undi95/toppy-m-7b","name":"Toppy M 7B","created":1699574400,"description":"A wild 7B parameter model that merges several models using the new task_arithmetic merge method from mergekit.\nList of merged models:\n- NousResearch/Nous-Capybara-7B-V1.9\n- [HuggingFaceH4/zephyr-7b-beta](/models/huggingfaceh4/zephyr-7b-beta)\n- lemonilia/AshhLimaRP-Mistral-7B\n- Vulkane/120-Days-of-Sodom-LoRA-Mistral-7b\n- Undi95/Mistral-pippa-sharegpt-7b-qlora\n\n#merge #uncensored","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"alpaca"},"pricing":{"prompt":"0.00000007","completion":"0.00000007","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"alpindale/goliath-120b","name":"Goliath 120B","created":1699574400,"description":"A large LLM created by combining two fine-tuned Llama 70B models into one 120B model. Combines Xwin and Euryale.\n\nCredits to\n- [@chargoddard](https://huggingface.co/chargoddard) for developing the framework used to merge the model - [mergekit](https://github.com/cg123/mergekit).\n- [@Undi95](https://huggingface.co/Undi95) for helping with the merge ratios.\n\n#merge","context_length":6144,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"airoboros"},"pricing":{"prompt":"0.000009375","completion":"0.000009375","image":"0","request":"0"},"top_provider":{"context_length":6144,"max_completion_tokens":512,"is_moderated":false},"per_request_limits":null},{"id":"openrouter/auto","name":"Auto Router (best for prompt)","created":1699401600,"description":"Your prompt will be processed by a meta-model and routed to one of dozens of models (see below), optimizing for the best possible output.\n\nTo see which model was used, visit [Activity](/activity), or read the `model` attribute of the response. Your response will be priced at the same rate as the routed model.\n\nLearn more about how Not Diamond's meta-model works [here](https://docs.notdiamond.ai/docs/how-not-diamond-works).\n\nRequests will be routed to the following models:\n- [openai/gpt-4o-2024-08-06](/openai/gpt-4o-2024-08-06)\n- [openai/gpt-4o-2024-05-13](/openai/gpt-4o-2024-05-13)\n- [openai/gpt-4o-mini-2024-07-18](/openai/gpt-4o-mini-2024-07-18)\n- [openai/chatgpt-4o-latest](/openai/chatgpt-4o-latest)\n- [openai/o1-preview-2024-09-12](/openai/o1-preview-2024-09-12)\n- [openai/o1-mini-2024-09-12](/openai/o1-mini-2024-09-12)\n- [anthropic/claude-3.5-sonnet](/anthropic/claude-3.5-sonnet)\n- [anthropic/claude-3.5-haiku](/anthropic/claude-3.5-haiku)\n- [anthropic/claude-3-opus](/anthropic/claude-3-opus)\n- [anthropic/claude-2.1](/anthropic/claude-2.1)\n- [google/gemini-pro-1.5](/google/gemini-pro-1.5)\n- [google/gemini-flash-1.5](/google/gemini-flash-1.5)\n- [mistralai/mistral-large-2407](/mistralai/mistral-large-2407)\n- [mistralai/mistral-nemo](/mistralai/mistral-nemo)\n- [meta-llama/llama-3.1-70b-instruct](/meta-llama/llama-3.1-70b-instruct)\n- [meta-llama/llama-3.1-405b-instruct](/meta-llama/llama-3.1-405b-instruct)\n- [mistralai/mixtral-8x22b-instruct](/mistralai/mixtral-8x22b-instruct)\n- [cohere/command-r-plus](/cohere/command-r-plus)\n- [cohere/command-r](/cohere/command-r)","context_length":2000000,"architecture":{"modality":"text->text","tokenizer":"Router","instruct_type":null},"pricing":{"prompt":"-1","completion":"-1","request":"-1","image":"-1"},"top_provider":{"context_length":null,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo-1106","name":"OpenAI: GPT-3.5 Turbo 16k (older v1106)","created":1699228800,"description":"An older GPT-3.5 Turbo model with improved instruction following, JSON mode, reproducible outputs, parallel function calling, and more. Training data: up to Sep 2021.","context_length":16385,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":16385,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4-1106-preview","name":"OpenAI: GPT-4 Turbo (older v1106)","created":1699228800,"description":"The latest GPT-4 Turbo model with vision capabilities. Vision requests can now use JSON mode and function calling.\n\nTraining data: up to April 2023.","context_length":128000,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00001","completion":"0.00003","image":"0","request":"0"},"top_provider":{"context_length":128000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"google/palm-2-chat-bison-32k","name":"Google: PaLM 2 Chat 32k","created":1698969600,"description":"PaLM 2 is a language model by Google with improved multilingual, reasoning and coding capabilities.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"PaLM","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"google/palm-2-codechat-bison-32k","name":"Google: PaLM 2 Code Chat 32k","created":1698969600,"description":"PaLM 2 fine-tuned for chatbot conversations that help with code-related questions.","context_length":32768,"architecture":{"modality":"text->text","tokenizer":"PaLM","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":32768,"max_completion_tokens":8192,"is_moderated":false},"per_request_limits":null},{"id":"jondurbin/airoboros-l2-70b","name":"Airoboros 70B","created":1698537600,"description":"A Llama 2 70B fine-tune using synthetic data (the Airoboros dataset).\n\nCurrently based on [jondurbin/airoboros-l2-70b](https://huggingface.co/jondurbin/airoboros-l2-70b-2.2.1), but might get updated in the future.","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"airoboros"},"pricing":{"prompt":"0.0000005","completion":"0.0000005","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"xwin-lm/xwin-lm-70b","name":"Xwin 70B","created":1697328000,"description":"Xwin-LM aims to develop and open-source alignment tech for LLMs. Our first release, built-upon on the [Llama2](/models/${Model.Llama_2_13B_Chat}) base models, ranked TOP-1 on AlpacaEval. Notably, it's the first to surpass [GPT-4](/models/${Model.GPT_4}) on this benchmark. The project will be continuously updated.","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"airoboros"},"pricing":{"prompt":"0.00000375","completion":"0.00000375","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":512,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo-instruct","name":"OpenAI: GPT-3.5 Turbo Instruct","created":1695859200,"description":"This model is a variant of GPT-3.5 Turbo tuned for instructional prompts and omitting chat-related optimizations. Training data: up to Sep 2021.","context_length":4095,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":"chatml"},"pricing":{"prompt":"0.0000015","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":4095,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"mistralai/mistral-7b-instruct-v0.1","name":"Mistral: Mistral 7B Instruct v0.1","created":1695859200,"description":"A 7.3B parameter model that outperforms Llama 2 13B on all benchmarks, with optimizations for speed and context length.","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"mistral"},"pricing":{"prompt":"0.00000018","completion":"0.00000018","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"pygmalionai/mythalion-13b","name":"Pygmalion: Mythalion 13B","created":1693612800,"description":"A blend of the new Pygmalion-13b and MythoMax. #merge","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.0000008","completion":"0.0000012","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo-16k","name":"OpenAI: GPT-3.5 Turbo 16k","created":1693180800,"description":"This model offers four times the context length of gpt-3.5-turbo, allowing it to support approximately 20 pages of text in a single request at a higher cost. Training data: up to Sep 2021.","context_length":16385,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.000003","completion":"0.000004","image":"0","request":"0"},"top_provider":{"context_length":16385,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4-32k","name":"OpenAI: GPT-4 32k","created":1693180800,"description":"GPT-4-32k is an extended version of GPT-4, with the same capabilities but quadrupled context length, allowing for processing up to 40 pages of text in a single pass. This is particularly beneficial for handling longer content like interacting with PDFs without an external vector database. Training data: up to Sep 2021.","context_length":32767,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00006","completion":"0.00012","image":"0","request":"0"},"top_provider":{"context_length":32767,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4-32k-0314","name":"OpenAI: GPT-4 32k (older v0314)","created":1693180800,"description":"GPT-4-32k is an extended version of GPT-4, with the same capabilities but quadrupled context length, allowing for processing up to 40 pages of text in a single pass. This is particularly beneficial for handling longer content like interacting with PDFs without an external vector database. Training data: up to Sep 2021.","context_length":32767,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00006","completion":"0.00012","image":"0","request":"0"},"top_provider":{"context_length":32767,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"nousresearch/nous-hermes-llama2-13b","name":"Nous: Hermes 13B","created":1692489600,"description":"A state-of-the-art language model fine-tuned on over 300k instructions by Nous Research, with Teknium and Emozilla leading the fine tuning process.","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.00000017","completion":"0.00000017","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"mancer/weaver","name":"Mancer: Weaver (alpha)","created":1690934400,"description":"An attempt to recreate Claude-style verbosity, but don't expect the same level of coherence or memory. Meant for use in roleplay/narrative situations.","context_length":8000,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.0000015","completion":"0.00000225","image":"0","request":"0"},"top_provider":{"context_length":8000,"max_completion_tokens":1000,"is_moderated":false},"per_request_limits":null},{"id":"huggingfaceh4/zephyr-7b-beta:free","name":"Hugging Face: Zephyr 7B (free)","created":1690934400,"description":"Zephyr is a series of language models that are trained to act as helpful assistants. Zephyr-7B-β is the second model in the series, and is a fine-tuned version of [mistralai/Mistral-7B-v0.1](/models/mistralai/mistral-7b-instruct-v0.1) that was trained on a mix of publicly available, synthetic datasets using Direct Preference Optimization (DPO).","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Mistral","instruct_type":"zephyr"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-2.0:beta","name":"Anthropic: Claude v2.0 (self-moderated)","created":1690502400,"description":"Anthropic's flagship model. Superior performance on tasks that require complex reasoning. Supports hundreds of pages of text.","context_length":100000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":100000,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"anthropic/claude-2.0","name":"Anthropic: Claude v2.0","created":1690502400,"description":"Anthropic's flagship model. Superior performance on tasks that require complex reasoning. Supports hundreds of pages of text.","context_length":100000,"architecture":{"modality":"text->text","tokenizer":"Claude","instruct_type":null},"pricing":{"prompt":"0.000008","completion":"0.000024","image":"0","request":"0"},"top_provider":{"context_length":100000,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"undi95/remm-slerp-l2-13b","name":"ReMM SLERP 13B","created":1689984000,"description":"A recreation trial of the original MythoMax-L2-B13 but with updated models. #merge","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.0000008","completion":"0.0000012","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"undi95/remm-slerp-l2-13b:extended","name":"ReMM SLERP 13B (extended)","created":1689984000,"description":"A recreation trial of the original MythoMax-L2-B13 but with updated models. #merge","context_length":6144,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.000001125","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":6144,"max_completion_tokens":512,"is_moderated":false},"per_request_limits":null},{"id":"google/palm-2-chat-bison","name":"Google: PaLM 2 Chat","created":1689811200,"description":"PaLM 2 is a language model by Google with improved multilingual, reasoning and coding capabilities.","context_length":9216,"architecture":{"modality":"text->text","tokenizer":"PaLM","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":9216,"max_completion_tokens":1024,"is_moderated":false},"per_request_limits":null},{"id":"google/palm-2-codechat-bison","name":"Google: PaLM 2 Code Chat","created":1689811200,"description":"PaLM 2 fine-tuned for chatbot conversations that help with code-related questions.","context_length":7168,"architecture":{"modality":"text->text","tokenizer":"PaLM","instruct_type":null},"pricing":{"prompt":"0.000001","completion":"0.000002","image":"0","request":"0"},"top_provider":{"context_length":7168,"max_completion_tokens":1024,"is_moderated":false},"per_request_limits":null},{"id":"gryphe/mythomax-l2-13b:free","name":"MythoMax 13B (free)","created":1688256000,"description":"One of the highest performing and most popular fine-tunes of Llama 2 13B, with rich descriptions and roleplay. #merge","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0","completion":"0","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":2048,"is_moderated":false},"per_request_limits":null},{"id":"gryphe/mythomax-l2-13b","name":"MythoMax 13B","created":1688256000,"description":"One of the highest performing and most popular fine-tunes of Llama 2 13B, with rich descriptions and roleplay. #merge","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.000000065","completion":"0.000000065","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":4096,"is_moderated":false},"per_request_limits":null},{"id":"gryphe/mythomax-l2-13b:nitro","name":"MythoMax 13B (nitro)","created":1688256000,"description":"One of the highest performing and most popular fine-tunes of Llama 2 13B, with rich descriptions and roleplay. #merge","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.0000002","completion":"0.0000002","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"gryphe/mythomax-l2-13b:extended","name":"MythoMax 13B (extended)","created":1688256000,"description":"One of the highest performing and most popular fine-tunes of Llama 2 13B, with rich descriptions and roleplay. #merge","context_length":8192,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"alpaca"},"pricing":{"prompt":"0.000001125","completion":"0.000001125","image":"0","request":"0"},"top_provider":{"context_length":8192,"max_completion_tokens":512,"is_moderated":false},"per_request_limits":null},{"id":"meta-llama/llama-2-13b-chat","name":"Meta: Llama 2 13B Chat","created":1687219200,"description":"A 13 billion parameter language model from Meta, fine tuned for chat completions","context_length":4096,"architecture":{"modality":"text->text","tokenizer":"Llama2","instruct_type":"llama2"},"pricing":{"prompt":"0.000000198","completion":"0.000000198","image":"0","request":"0"},"top_provider":{"context_length":4096,"max_completion_tokens":null,"is_moderated":false},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo","name":"OpenAI: GPT-3.5 Turbo","created":1685232000,"description":"GPT-3.5 Turbo is OpenAI's fastest model. It can understand and generate natural language or code, and is optimized for chat and traditional completion tasks.\n\nTraining data up to Sep 2021.","context_length":16385,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.0000005","completion":"0.0000015","image":"0","request":"0"},"top_provider":{"context_length":16385,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-3.5-turbo-0125","name":"OpenAI: GPT-3.5 Turbo 16k","created":1685232000,"description":"The latest GPT-3.5 Turbo model with improved instruction following, JSON mode, reproducible outputs, parallel function calling, and more. Training data: up to Sep 2021.\n\nThis version has a higher accuracy at responding in requested formats and a fix for a bug which caused a text encoding issue for non-English language function calls.","context_length":16385,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.0000005","completion":"0.0000015","image":"0","request":"0"},"top_provider":{"context_length":16385,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4","name":"OpenAI: GPT-4","created":1685232000,"description":"OpenAI's flagship model, GPT-4 is a large-scale multimodal language model capable of solving difficult problems with greater accuracy than previous models due to its broader general knowledge and advanced reasoning capabilities. Training data: up to Sep 2021.","context_length":8191,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00003","completion":"0.00006","image":"0","request":"0"},"top_provider":{"context_length":8191,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null},{"id":"openai/gpt-4-0314","name":"OpenAI: GPT-4 (older v0314)","created":1685232000,"description":"GPT-4-0314 is the first version of GPT-4 released, with a context length of 8,192 tokens, and was supported until June 14. Training data: up to Sep 2021.","context_length":8191,"architecture":{"modality":"text->text","tokenizer":"GPT","instruct_type":null},"pricing":{"prompt":"0.00003","completion":"0.00006","image":"0","request":"0"},"top_provider":{"context_length":8191,"max_completion_tokens":4096,"is_moderated":true},"per_request_limits":null}]} ================================================ FILE: packages/backend/resources/openapi/stabilityai.json ================================================ { "openapi": "3.0.3", "info": { "version": "v2beta", "title": "StabilityAI REST API", "description": "Welcome to the Stability Platform API. As of March 2024, we are building the REST v2beta API service to be the primary API service for the Stability Platform. \nAll AI services on other APIs (gRPC, REST v1, RESTv2alpha) will continue to be maintained, however they will not receive\nnew features or parameters.\n\nIf you are a REST v2alpha user, we strongly recommend that you adjust the URL calls for the specific services that you are using over to the equivalent REST v2beta URL. Normally, this means simply replacing \"v2alpha\" with \"v2beta\". We are not deprecating v2alpha URLs at this time for users that are currently using them.\n\n#### Authentication\n\nYou will need your [Stability API key](https://platform.stability.ai/account/keys) in order to make requests to this API.\nMake sure you never share your API key with anyone, and you never commit it to a public repository. Include this key in \nthe `Authorization` header of your requests.\n\n#### Rate limiting\n\nThis API is rate-limited to 150 requests every 10 seconds. If you exceed this limit, you will receive a `429` response\nand be timed out for 60 seconds. If you find this limit too restrictive, please reach out to us via [this form](https://stabilityplatform.freshdesk.com/support/home).\n\n#### Support\n\nPlease see our [FAQ](https://platform.stability.ai/faq) for answers to common questions. If you have any other questions or concerns,\nplease reach out to us via [this form](https://stabilityplatform.freshdesk.com/support/tickets/new).\n\nTo see the health of our APIs, please check our [Status Page](https://stabilityai.instatus.com/)." }, "servers": [ { "url": "https://api.stability.ai" } ], "security": [ { "STABILITY_API_KEY": [] } ], "tags": [ { "name": "Edit", "description": "Tools for editing your own and generated images.\n\n**[Erase](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1erase/post)**\n\nThe Erase service removes unwanted objects, such as blemishes on portraits or items on desks, using image masks.\n\n**[Outpaint](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1outpaint/post)**\n\nThe outpaint service inserts additional content in an image to fill in the space in any direction, allowing you to \"zoom-out\" of an image.\n\n**[Inpaint](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1inpaint/post)**\n\nThe Inpaint service modifies images by filling in or replacing specified areas with new content based on the content of a \"mask\" image.\n\n**[Search and Replace](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1search-and-replace/post)**\n\nThe Search and Replace service, similar to inpaint, allows to replace specified areas with new content, but this time with the help of a prompt instead of a mask. The service will automatically segment the object and replace it with the object requested in the prompt.\n\n**[Search and Recolor](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1search-and-recolor/post)**\n\nThe Search and Recolor service is another derivative of the inpaint service and provides the ability to change the color of a specific object in an image using a prompt. The Search and Recolor service will automatically segment the object and recolor it using the colors requested in the prompt.\n\n**[Remove Background](/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1remove-background/post)**\n\nThe Remove Background service accurately segments the foreground from an image to removes the background." }, { "name": "Upscale", "description": "Tools for increasing the size and resolution of your existing images.\n\n**[Fast Upscaler](/docs/api-reference#tag/Upscale/paths/~1v2beta~1stable-image~1upscale~1fast/post)**\n\nThis service enhances image resolution by 4x using predictive and generative AI. This lightweight and fast service (processing in ~1 second) is ideal for enhancing the quality of compressed images, making it suitable for social media posts and other applications.\n\n**[Conservative Upscaler](/docs/api-reference#tag/Upscale/paths/~1v2beta~1stable-image~1upscale~1conservative/post)**\n\nThis service can upscale images by 20 to 40 times up to a 4 megapixel output image with minimal alteration to the original image. The Conservative Upscaler can upscale images as small as 64x64 pixels directly to a 4 megapixel output. Use this option if you directly need a 4 megapixel output.\n\n**[Creative Upscaler](/docs/api-reference#tag/Upscale/paths/~1v2beta~1stable-image~1upscale~1creative/post)**\n\nThe service can upscale highly degraded images (lower than 1 megapixel) with a creative twist to provide high resolution results." }, { "name": "Generate", "description": "Tools to generate new images from text, or create variations of existing images. Our different services include:\n\n**[Stable Image Ultra](/docs/api-reference#tag/Generate/paths/~1v2beta~1stable-image~1generate~1ultra/post)**: Photorealistic, Large-Scale Output\n\nOur state of the art text to image model based on Stable Diffusion 3.5. Stable Image Ultra Produces the highest quality, photorealistic outputs perfect for professional print media and large format applications. Stable Image Ultra excels at rendering exceptional detail and realism.\n\n**[Stable Image Core](/docs/api-reference#tag/Generate/paths/~1v2beta~1stable-image~1generate~1core/post)**: Fast and Affordable\n\nOptimized for fast and affordable image generation, great for rapidly iterating on concepts during ideation. Stable Image Core is the next generation model following Stable Diffusion XL.\n\n**[Stable Diffusion 3 & 3.5 Model Suite](/docs/api-reference#tag/Generate/paths/~1v2beta~1stable-image~1generate~1sd3/post)**: Stability AI's latest base models\n\nThe different versions of our open models are available via API, letting you test and adjust speed and quality based on your use case. All model versions strike a balance between generation speed and output quality and are ideal for creating high-volume, high-quality digital assets like websites, newsletters, and marketing materials." }, { "name": "Control", "description": "Tools for generating precise, controlled variations of existing images or sketches.\n\n**[Sketch](/docs/api-reference#tag/Control/paths/~1v2beta~1stable-image~1control~1sketch/post)**\n\nThis service upgrades sketches to refined outputs with precise control. For non-sketch images, it allows detailed manipulation of the final appearance by leveraging the contour lines and edges within the image. \n\n**[Structure](/docs/api-reference#tag/Control/paths/~1v2beta~1stable-image~1control~1structure/post)**\n\nThis service excels in generating images by maintaining the structure of an input image, making it especially valuable for advanced content creation scenarios such as recreating scenes or rendering characters from models.\n\n**[Style](/docs/api-reference#tag/Control/paths/~1v2beta~1stable-image~1control~1style/post)**\n\nThis service extracts stylistic elements from an input image (control image) and uses it to guide the creation of an output image based on the prompt. The result is a new image in the same style as the control image." }, { "name": "Results", "description": "Tools for fetching the results of your async generations." }, { "name": "User", "description": "Manage your Stability account, and view account/organization balances." }, { "name": "Engines", "description": "Enumerate engines that work with 'Version 1' REST API endpoints." }, { "name": "SDXL 1.0 & SD1.6", "description": "Generate images using SDXL 1.0 or SD1.6." } ], "paths": { "/v2alpha/generation/image-to-video": { "post": { "tags": ["v2alpha/generation"], "summary": "image-to-video", "description": "Generate a short video based on an initial image with [Stable Video Diffusion](https://static1.squarespace.com/static/6213c340453c3f502425776e/t/655ce779b9d47d342a93c890/1700587395994/stable_video_diffusion.pdf),\na latent video diffusion model. \n\n\n\n### How to generate a video\nVideo generations are asynchronous, so after starting a generation use the `id` returned in the response to poll [/v2alpha/generation/image-to-video/result/{id}](#tag/v2alphageneration/paths/~1v2alpha~1generation~1image-to-video~1result~1%7Bid%7D/get) for results.\n\n### Price\nFlat rate of 20 cents per generation.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2alpha/generation/image-to-video\",\n headers={\"authorization\": f\"Bearer sk-MYAPIKEY\"},\n files={\"image\": open(\"./kittens-in-space.png\", \"rb\")},\n data={\n \"seed\": 0,\n \"cfg_scale\": 1.8,\n \"motion_bucket_id\": 127\n },\n)\n\nprint(\"Generation ID:\", response.json().get('id'))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst data = new FormData();\ndata.append(\"image\", fs.readFileSync(\"./image.png\"), \"image.png\");\ndata.append(\"seed\", 0);\ndata.append(\"cfg_scale\", 1.8);\ndata.append(\"motion_bucket_id\", 127);\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2alpha/generation/image-to-video`,\n method: \"post\",\n validateStatus: undefined,\n headers: {\n authorization: `Bearer sk-MYAPIKEY`,\n ...data.getHeaders(),\n },\n data: data,\n});\n\nconsole.log(\"Generation ID:\", response.data.id);" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2alpha/generation/image-to-video\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./image.png\" \\\n -F seed=0 \\\n -F cfg_scale=1.8 \\\n -F motion_bucket_id=127 \\\n -o \"./output.json\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/ImageToVideoRequest" } } } }, "responses": { "200": { "description": "Video generation started. Poll for results using the `id` in the response [here](#tag/v2alphageneration/paths/~1v2alpha~1generation~1image-to-video~1result~1%7Bid%7D/get).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" } }, "required": ["id"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2alpha/generation/image-to-video/result/{id}": { "get": { "tags": ["v2alpha/generation"], "summary": "image-to-video/result", "description": "Fetch the result of an image-to-video generation by ID. Make sure you use the same API key that you used to\ngenerate the video, otherwise you will receive a `404` response.\n\n### How is progress reported?\nYour generation is either `in-progress` (i.e. status code `202`) or it is complete (i.e. status code `200`). \nWe may add more fine-grained progress reporting in the future (e.g. a numerical progress).\n\n### How long are results stored?\nResults are stored for 24 hours after generation. After that, the results are deleted and you will need to \nre-generate your video.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\ngeneration_id = \"e52772ac75b...\"\n\nresponse = requests.request(\n \"GET\",\n f\"https://api.stability.ai/v2alpha/generation/image-to-video/result/{generation_id}\",\n headers={\n 'Accept': \"video/*\", # Use 'application/json' to receive base64 encoded JSON\n 'authorization': f\"Bearer sk-MYAPIKEY\"\n },\n)\n\nif response.status_code == 202:\n print(\"Generation in-progress, try again in 10 seconds.\")\nelif response.status_code == 200:\n print(\"Generation complete!\")\n with open(\"video.mp4\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport fs from \"node:fs\";\n\nconst generationID = \"e52772ac75b...\";\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2alpha/generation/image-to-video/result/${generationID}`,\n method: \"GET\",\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n accept: \"video/*\", // Use 'application/json' to receive base64 encoded JSON\n authorization: `Bearer sk-MYAPIKEY`,\n },\n});\n\nif (response.status === 202) {\n console.log(\"Generation is still running, try again in 10 seconds.\");\n} else if (response.status === 200) {\n console.log(\"Generation is complete!\");\n fs.writeFileSync(\"video.mp4\", Buffer.from(response.data));\n} else {\n throw new Error(`Response ${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "generation_id=\"e52772ac75b...\"\nurl=\"https://api.stability.ai/v2alpha/generation/image-to-video/result/$generation_id\"\nhttp_status=$(curl -sS -f -o \"./output.mp4\" -w '%{http_code}' -H \"authorization: sk-MYAPIKEY\" -H 'accept: video/*' \"$url\")\n\ncase $http_status in\n 202)\n echo \"Still processing. Retrying in 10 seconds...\"\n ;;\n 200)\n echo \"Download complete!\"\n ;;\n 4*|5*)\n mv \"./output.mp4\" \"./error.json\"\n echo \"Error: Check ./error.json for details.\"\n exit 1\n ;;\nesac" } ], "parameters": [ { "schema": { "$ref": "#/components/schemas/GenerationID" }, "required": true, "name": "id", "in": "path" }, { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "default": "video/*", "description": "Specify `video/*` to get the video bytes directly. Otherwise specify `application/json` to receive the video as base64 encoded JSON.", "enum": ["video/*", "application/json"] }, "required": false, "name": "accept", "in": "header" } ], "responses": { "200": { "description": "The result of your video generation.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated video.\n\n To receive the bytes of the video directly, specify `video/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "mp4": { "description": "raw bytes", "value": "video/mp4" }, "mp4JSON": { "description": "base64 encoded", "value": "application/json; type=video/mp4" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and one or more frames have been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "video/mp4": { "schema": { "type": "string", "description": "The bytes of the generated video.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated mp4.\n(Caution: may contain cats)" }, "application/json; type=video/mp4": { "schema": { "type": "object", "properties": { "video": { "type": "string", "description": "The generated video, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and one or more frames have been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["video", "finish_reason"] } } } }, "202": { "description": "Your image-to-video generation is still in-progress.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" }, "status": { "type": "string", "enum": ["in-progress"], "description": "The status of your generation." } }, "required": ["id", "status"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "404": { "description": "id: the generation either does not exist or has expired.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2bca35116bc5431d6dc4b4ea2ef3da2f", "name": "generation_not_found", "errors": [ "id: the generation either does not exist or has expired." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2alpha/generation/stable-image/upscale": { "post": { "tags": ["v2alpha/generation"], "summary": "stable-image/upscale", "description": "Takes images between 64x64 and 1 megapixel and upscales them all the way to **4K** resolution. Put more \ngenerally, it can upscale images ~20-40x times while preserving, and often enhancing, quality.\n\n### How to use\n - Invoke this endpoint with the required parameters to start a generation\n - Use that `id` in the response to poll for results at the [upscale/result/{id}](#tag/v2alphageneration/paths/~1v2alpha~1generation~1stable-image~1upscale~1result~1%7Bid%7D/get) endpoint\n - Rate-limiting or other errors may occur if you poll more than once every 10 seconds\n \n### Price\nFlat rate of 25 cents per generation.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2alpha/generation/stable-image/upscale\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\"\n },\n files={\n \"image\": open(\"./kitten-in-space.png\", \"rb\")\n },\n data={\n \"prompt\": \"cute fluffy white kitten floating in space, pastel colors\",\n \"output_format\": \"webp\",\n },\n)\n\nprint(\"Generation ID:\", response.json().get('id'))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst formData = {\n image: fs.createReadStream(\"./kitten-in-space.png\"),\n prompt: \"cute fluffy white kitten floating in space, pastel colors\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2alpha/generation/stable-image/upscale`,\n axios.toFormData(formData, new FormData()),\n {\n validateStatus: undefined,\n headers: { Authorization: `Bearer sk-MYAPIKEY` },\n },\n);\n\nconsole.log(\"Generation ID:\", response.data.id);" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2alpha/generation/stable-image/upscale\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./kitten-in-rainforest.png\" \\\n -F prompt=\"cute fluffy white kitten sitting in a rainforest, pastel colors\" \\\n -F output_format=webp \\\n -o \"./output.json\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to upscale.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 1,048,576 pixels", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "creativity": { "type": "number", "minimum": 0, "maximum": 0.35, "default": 0.3, "description": "Indicates how creative the model should be when upscaling an image.\nHigher values will result in more details being added to the image during upscaling." } }, "required": ["image", "prompt"] } } } }, "responses": { "200": { "description": "Upscaling was successful!", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" } }, "required": ["id"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2alpha/generation/stable-image/upscale/result/{id}": { "get": { "tags": ["v2alpha/generation"], "summary": "stable-image/upscale/result", "description": "Fetch the result of an upscale generation by ID. Make sure to use the same API key to fetch the generation result\nthat you used to create the generation, otherwise you will receive a `404` response.\n\n### How is progress reported?\nYour generation is either `in-progress` (i.e. status code `202`) or it is complete (i.e. status code `200`). \nWe may add more fine-grained progress reporting in the future (e.g. a numerical progress).\n\n### How long are results stored?\nResults are stored for 24 hours after generation. After that, the results are deleted.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\ngeneration_id = \"e52772ac75b...\"\n\nresponse = requests.request(\n \"GET\",\n f\"https://api.stability.ai/v2alpha/generation/stable-image/upscale/result/{generation_id}\",\n headers={\n 'Accept': \"image/*\", # Use 'application/json' to receive base64 encoded JSON\n 'authorization': f\"Bearer sk-MYAPIKEY\"\n },\n)\n\nif response.status_code == 202:\n print(\"Generation in-progress, try again in 10 seconds.\")\nelif response.status_code == 200:\n print(\"Generation complete!\")\n with open(\"upscaled.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport fs from \"node:fs\";\n\nconst generationID = \"e52772ac75b...\";\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2alpha/generation/stable-image/upscale/result/${generationID}`,\n method: \"GET\",\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n accept: \"image/*\", // Use 'application/json' to receive base64 encoded JSON\n authorization: `Bearer sk-MYAPIKEY`,\n },\n});\n\nif (response.status === 202) {\n console.log(\"Generation is still running, try again in 10 seconds.\");\n} else if (response.status === 200) {\n console.log(\"Generation is complete!\");\n fs.writeFileSync(\"upscaled.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`Response ${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "generation_id=\"e52772ac75b...\"\nurl=\"https://api.stability.ai/v2alpha/generation/stable-image/upscale/result/$generation_id\"\nhttp_status=$(curl -sS -f -o \"./upscaled.webp\" -w '%{http_code}' -H \"authorization: sk-MYAPIKEY\" -H 'accept: image/*' \"$url\")\n\ncase $http_status in\n 202)\n echo \"Still processing. Retrying in 10 seconds...\"\n ;;\n 200)\n echo \"Download complete!\"\n ;;\n 4*|5*)\n mv \"./upscaled.webp\" \"./error.json\"\n echo \"Error: Check ./error.json for details.\"\n exit 1\n ;;\nesac" } ], "parameters": [ { "schema": { "$ref": "#/components/schemas/GenerationID" }, "required": true, "name": "id", "in": "path" }, { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to get the image bytes directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" } ], "responses": { "200": { "description": "The upscale was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "202": { "description": "Your upscale generation is still in-progress.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" }, "status": { "type": "string", "enum": ["in-progress"], "description": "The status of your generation." } }, "required": ["id", "status"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "404": { "description": "id: the generation either does not exist or has expired.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2bca35116bc5431d6dc4b4ea2ef3da2f", "name": "generation_not_found", "errors": [ "id: the generation either does not exist or has expired." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2alpha/generation/stable-image/inpaint": { "post": { "tags": ["v2alpha/generation"], "summary": "stable-image/inpaint", "description": "Inpaint an existing image, with or without a mask, using our latest-and-greatest inpainting model.\n\n### Search-and-Replace Mode\nThis mode is ideal for individuals of all levels of skill in design. It can be used for straightforward \nadjustments to images. The service will automatically mask the most appropriate object based on the contents\nof the `search_prompt`, and replace it with a generated result based on the `prompt`.\n\n**How to use:** set the `mode` parameter to `search` and provide a short description of what to \nsearch-and-replace in the `search_prompt` parameter.\n\n### Mask Mode\nThis mode allows for precise control of generative fill tasks on an image, down to the level of \nindividual pixels. Design professionals can provide a `mask` for the section of the image to be replaced, \nand use standard image prompting to describe the full image as it should appear after the editing. \nThe resulting image will incorporate all of the elements described in the `prompt`.\n\n**How to use:** set the `mode` parameter to `mask` and either pass in an `image` with an alpha channel \nor provide an explicit mask image to the `mask` parameter. If both are present the `mask` parameter will\ntake precedence.\n\n### Price\n- Requests with `mode` set to `search` cost 4 cents.\n- Requests with `mode` set to `mask` cost 3 cents.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2alpha/generation/stable-image/inpaint\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\"\n },\n files={\n \"image\": open(\"./husky-in-a-field.png\", \"rb\")\n },\n data={\n \"prompt\": \"golden retriever in a field\",\n \"mode\": \"search\",\n \"search_prompt\": \"dog\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./golden-retriever-in-a-field.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst formData = {\n image: fs.createReadStream(\"./husky-in-a-field.png\"),\n prompt: \"golden retriever standing in a field\",\n mode: \"search\",\n search_prompt: \"dog\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2alpha/generation/stable-image/inpaint`,\n axios.toFormData(formData, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { Authorization: `Bearer sk-MYAPIKEY`, accept: \"image/*\" },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./golden-retriever-in-a-field.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2alpha/generation/stable-image/inpaint\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./husky-in-a-field.png\" \\\n -F prompt=\"golden retriever in a field\" \\\n -F mode=\"search\" \\\n -F search_prompt=\"dog\" \\\n -F output_format=\"webp\" \\\n -o \"./golden-retriever-in-a-field.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to get the image bytes directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/InpaintingSearchModeRequestBody" }, { "$ref": "#/components/schemas/InpaintingMaskingModeRequestBody" } ], "discriminator": { "propertyName": "mode", "mapping": { "search": "#/components/schemas/InpaintingSearchModeRequestBody", "mask": "#/components/schemas/InpaintingMaskingModeRequestBody" } } } } } }, "responses": { "200": { "description": "Inpainting was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/image-to-video": { "post": { "tags": ["Image-to-Video"], "summary": "Start generation", "description": "Generate a short video based on an initial image with [Stable Video Diffusion](https://static1.squarespace.com/static/6213c340453c3f502425776e/t/655ce779b9d47d342a93c890/1700587395994/stable_video_diffusion.pdf),\na latent video diffusion model. \n\n\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`.\n\nThe body of the request should include:\n- `image`\n\nThe body may optionally include:\n- `seed`\n- `cfg_scale`\n- `motion_bucket_id`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\nAfter invoking this endpoint with the required parameters, use the `id` in the response to poll for results at the\n[image-to-video/result/{id}](#tag/Image-to-Video/paths/~1v2beta~1image-to-video~1result~1%7Bid%7D/get) endpoint. Rate-limiting or other errors may occur if you poll more than once every 10 seconds.\n\n### Credits\nFlat rate of 20 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/image-to-video\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\"\n },\n files={\n \"image\": open(\"./kittens-in-space.png\", \"rb\")\n },\n data={\n \"seed\": 0,\n \"cfg_scale\": 1.8,\n \"motion_bucket_id\": 127\n },\n)\n\nprint(\"Generation ID:\", response.json().get('id'))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst data = new FormData();\ndata.append(\"image\", fs.readFileSync(\"./image.png\"), \"image.png\");\ndata.append(\"seed\", 0);\ndata.append(\"cfg_scale\", 1.8);\ndata.append(\"motion_bucket_id\", 127);\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2beta/image-to-video`,\n method: \"post\",\n validateStatus: undefined,\n headers: {\n authorization: `Bearer sk-MYAPIKEY`,\n ...data.getHeaders(),\n },\n data: data,\n});\n\nconsole.log(\"Generation ID:\", response.data.id);" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/image-to-video\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./image.png\" \\\n -F seed=0 \\\n -F cfg_scale=1.8 \\\n -F motion_bucket_id=127 \\\n -o \"./output.json\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/ImageToVideoRequest" } } } }, "responses": { "200": { "description": "Video generation started. Poll for results using the `id` in the response [here](#tag/Image-to-Video/paths/~1v2beta~1image-to-video~1result~1%7Bid%7D/get).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" } }, "required": ["id"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/image-to-video/result/{id}": { "get": { "tags": ["Image-to-Video"], "summary": "Fetch generation result", "description": "Fetch the result of an image-to-video generation by ID.\n\nMake sure to use the same API key to fetch the generation result that you used to create the generation, \notherwise you will receive a `404` response.\n\n### How to use\nPlease invoke this endpoint with a `GET` request.\n\nThe headers of the request must include an API key in the `authorization` field and the ID\nof your generation must be in the path.\n\n### How is progress reported?\nYour generation is either `in-progress` (i.e. status code `202`) or it is complete (i.e. status code `200`). \nWe may add more fine-grained progress reporting in the future (e.g. a numerical progress).\n\n### How long are results stored?\nResults are stored for 24 hours after generation. After that, the results are deleted and you will need to \nre-generate your video.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\ngeneration_id = \"e52772ac75b...\"\n\nresponse = requests.request(\n \"GET\",\n f\"https://api.stability.ai/v2beta/image-to-video/result/{generation_id}\",\n headers={\n 'accept': \"video/*\", # Use 'application/json' to receive base64 encoded JSON\n 'authorization': f\"Bearer sk-MYAPIKEY\"\n },\n)\n\nif response.status_code == 202:\n print(\"Generation in-progress, try again in 10 seconds.\")\nelif response.status_code == 200:\n print(\"Generation complete!\")\n with open(\"video.mp4\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport fs from \"node:fs\";\n\nconst generationID = \"e52772ac75b...\";\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2beta/image-to-video/result/${generationID}`,\n method: \"GET\",\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"video/*\", // Use 'application/json' to receive base64 encoded JSON\n },\n});\n\nif (response.status === 202) {\n console.log(\"Generation is still running, try again in 10 seconds.\");\n} else if (response.status === 200) {\n console.log(\"Generation is complete!\");\n fs.writeFileSync(\"video.mp4\", Buffer.from(response.data));\n} else {\n throw new Error(`Response ${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "generation_id=\"e52772ac75b...\"\nurl=\"https://api.stability.ai/v2beta/image-to-video/result/$generation_id\"\nhttp_status=$(curl -sS -f -o \"./output.mp4\" -w '%{http_code}' -H \"authorization: sk-MYAPIKEY\" -H 'accept: video/*' \"$url\")\n\ncase $http_status in\n 202)\n echo \"Still processing. Retrying in 10 seconds...\"\n ;;\n 200)\n echo \"Download complete!\"\n ;;\n 4*|5*)\n mv \"./output.mp4\" \"./error.json\"\n echo \"Error: Check ./error.json for details.\"\n exit 1\n ;;\nesac" } ], "parameters": [ { "schema": { "$ref": "#/components/schemas/GenerationID" }, "required": true, "name": "id", "in": "path" }, { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "default": "video/*", "description": "Specify `video/*` to receive the bytes of the video directly. Otherwise specify `application/json` to receive the video as base64 encoded JSON.", "enum": ["video/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "responses": { "200": { "description": "The result of your video generation.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated video.\n\n To receive the bytes of the video directly, specify `video/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "mp4": { "description": "raw bytes", "value": "video/mp4" }, "mp4JSON": { "description": "base64 encoded", "value": "application/json; type=video/mp4" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and one or more frames have been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "video/mp4": { "schema": { "type": "string", "description": "The bytes of the generated video.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated mp4.\n(Caution: may contain cats)" }, "application/json; type=video/mp4": { "schema": { "type": "object", "properties": { "video": { "type": "string", "description": "The generated video, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and one or more frames have been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["video", "finish_reason"] } } } }, "202": { "description": "Your image-to-video generation is still in-progress.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" }, "status": { "type": "string", "enum": ["in-progress"], "description": "The status of your generation." } }, "required": ["id", "status"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "404": { "description": "id: the generation either does not exist or has expired.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2bca35116bc5431d6dc4b4ea2ef3da2f", "name": "generation_not_found", "errors": [ "id: the generation either does not exist or has expired." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/3d/stable-fast-3d": { "post": { "tags": ["3D"], "summary": "Stable Fast 3D", "description": "Stable Fast 3D generates high-quality 3D assets from a single 2D input image.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_3D_API.ipynb)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`.\n\nThe body of the request should include:\n- `image`\n\nThe body may optionally include:\n- `texture_resolution`\n- `foreground_ratio`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe output is a binary blob that includes a glTF asset, including JSON, buffers, and images. \nSee the [GLB File Format Specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#glb-file-format-specification) for more details.\n\n### Credits\nFlat rate of 2 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/3d/stable-fast-3d\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n },\n files={\n \"image\": open(\"./cat-statue.png\", \"rb\")\n },\n data={},\n)\n\nif response.status_code == 200:\n with open(\"./3d-cat-statue.glb\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport FormData from \"form-data\";\nimport fs from \"node:fs\";\n\nconst payload = {\n image: fs.createReadStream(\"./cat-statue.png\"),\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/3d/stable-fast-3d`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n },\n },\n);\n\nif (response.status === 200) {\n fs.writeFileSync(\"./3d-cat-statue.glb\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/3d/stable-fast-3d\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./cat-statue.png\" \\\n -o \"./3d-cat-statue.glb\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image to generate a 3D model from.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 4,194,304 pixels", "format": "binary", "example": "./some/image.png" }, "texture_resolution": { "type": "string", "enum": ["512", "1024", "2048"], "default": "1024", "description": "Determines the resolution of the textures used for both the albedo (color) map\nand the normal map. The resolution is specified in pixels, and a higher value\ncorresponds to a higher level of detail in the textures, allowing for more\nintricate and precise rendering of surfaces. However, increasing the resolution\nalso results in larger asset sizes, which may impact loading times and\nperformance. 1024 is a good default value and rarely requires changing." }, "foreground_ratio": { "type": "number", "minimum": 0.1, "maximum": 1, "default": 0.85, "description": "Controls the amount of padding around the object to be processed within the frame.\nThis ratio determines the relative size of the object compared to the total frame\nsize. A higher ratio means less padding and a larger object, while a lower ratio\nincreases the padding, effectively reducing the object’s size within the frame. This\ncan be useful when a long and narrow object, such as a car or bus, is viewed from the\nfront (the narrow side). Here, lowering the foreground ratio might help prevent the\ngenerated 3D assets from appearing squished or distorted. The default value of 0.85 \nis good for most objects." }, "remesh": { "type": "string", "enum": ["none", "triangle", "quad"], "default": "none", "description": "Controls the remeshing algorithm used to generate the 3D model. The remeshing\nalgorithm determines how the 3D model is constructed from the input image. The\ndefault value of \"none\" means that the model is generated without remeshing,\nwhich is suitable for most use cases. The \"triangle\" option generates a model\nwith triangular faces, while the \"quad\" option generates a model with quadrilateral\nfaces. The \"quad\" option is useful when the 3D model will be used in DCC tools such\nas Maya or Blender." }, "vertex_count": { "type": "number", "minimum": -1, "maximum": 20000, "default": -1, "description": "If specified, the result will have approximately this many vertices (and consequently fewer faces) in the simplified mesh. \n\nSetting this value to -1 (the default value) means that a limit is not set." } }, "required": ["image"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "content-type": { "description": "The format of the 3D model.", "schema": { "type": "string", "example": "model/gltf-binary" } } }, "content": { "model/gltf-binary": { "schema": { "type": "string", "description": "The bytes of the generated 3D model.", "format": "binary" } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/3d/stable-point-aware-3d": { "post": { "tags": ["3D"], "summary": "Stable Point Aware 3D", "description": "Stable Point Aware 3D (SPAR3D) can make real-time edits and create the complete structure \nof a 3D object from a single image in a few seconds. SPAR3D combines the strengths of \npoint-cloud diffusion (probabilistic) and mesh regression (deterministic) to have improved \ndetails on the unseen back regions in the input image. \n\nCompared to our previous model [Stable Fast 3D](#tag/3D/paths/~1v2beta~13d~1stable-fast-3d/post), this new \none allows editing of backside information using the point cloud representation and also \nleverages a larger Diffusion model to generally improve the depth and backside \npredictions.\n\nRead more about the model capabilities [here](https://bit.ly/4h7cpgF). \n\nThis API is currently in \npreview. Please don’t hesitate to [contact us](https://stability.ai/contact) with any questions. \n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_3D_API.ipynb)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`.\n\nThe body of the request should include:\n- `image`\n\nThe body may optionally include:\n- `texture_resolution`\n- `foreground_ratio`\n- `remesh`\n- `target_type`\n- `target_count`\n- `guidance_scale`\n- `seed`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe output is a binary blob that includes a glTF asset, including JSON, buffers, and images. \nSee the [GLB File Format Specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#glb-file-format-specification) for more details.\n\n### Credits\nFlat rate of 4 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/3d/stable-point-aware-3d\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n },\n files={\n \"image\": open(\"./cat-statue.png\", \"rb\")\n },\n data={},\n)\n\nif response.status_code == 200:\n with open(\"./3d-cat-statue.glb\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport FormData from \"form-data\";\nimport fs from \"node:fs\";\n\nconst payload = {\n image: fs.createReadStream(\"./cat-statue.png\"),\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/3d/stable-point-aware-3d`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n },\n },\n);\n\nif (response.status === 200) {\n fs.writeFileSync(\"./3d-cat-statue.glb\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/3d/stable-point-aware-3d\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./cat-statue.png\" \\\n -o \"./3d-cat-statue.glb\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image to generate a 3D model from.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 4,194,304 pixels", "format": "binary", "example": "./some/image.png" }, "texture_resolution": { "type": "string", "enum": ["512", "1024", "2048"], "default": "1024", "description": "Determines the resolution of the textures used for both the albedo (color) map and the \nnormal map. The resolution is specified in pixels, and a higher value corresponds to a \nhigher level of detail in the textures, allowing for more intricate and precise rendering \nof surfaces. However, increasing the resolution also results in larger asset sizes, which \nmay impact loading times and performance. `1024` is a good default value and rarely requires \nchanging." }, "foreground_ratio": { "type": "number", "minimum": 1, "maximum": 2, "default": 1.3, "description": "Controls the amount of padding around the object to be processed within the frame. This \nratio determines the relative size of the object compared to the total frame size. A \nhigher ratio means less padding and a larger object, while a lower ratio increases the \npadding, effectively reducing the object’s size within the frame. This can be useful when \na long and narrow object, such as a car or bus, is viewed from the front (the narrow \nside). Here, lowering the foreground ratio might help prevent the generated 3D assets from \nappearing squished or distorted. The default value of `1.3` is good for most objects." }, "remesh": { "type": "string", "enum": ["none", "triangle", "quad"], "default": "none", "description": "Controls the remeshing algorithm used to generate the 3D model. The remeshing algorithm \ndetermines how the 3D model is constructed from the input image. The default value of \n\"none\" means that the model is generated without remeshing, which is suitable for most use \ncases. The \"triangle\" option generates a model with triangular faces, while the \"quad\" \noption generates a model with quadrilateral faces. The \"quad\" option is useful when the 3D \nmodel will be used in DCC tools such as Maya or Blender." }, "target_type": { "type": "string", "enum": ["none", "vertex", "face"], "default": "none", "description": "If set to `vertex` or `face`, the result will have approximately `target_count` many vertices or \nfaces in the simplified mesh, respectively." }, "target_count": { "type": "number", "minimum": 100, "maximum": 20000, "default": 1000, "description": "This sets the target vertex or face count defined by `target_type`. Selecting extremely low \ncounts reduces the quality of the mesh severely and values of 1,000 - 10,000 are recommended." }, "guidance_scale": { "type": "number", "minimum": 1, "maximum": 10, "default": 3, "description": "This sets the guidance scaling of the point diffusion module. Lower values produce less \ndetail and higher can introduce artifacts. The default of `3` produces best results." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" } }, "required": ["image"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "content-type": { "description": "The format of the 3D model.", "schema": { "type": "string", "example": "model/gltf-binary" } } }, "content": { "model/gltf-binary": { "schema": { "type": "string", "description": "The bytes of the generated 3D model.", "format": "binary" } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/results/{id}": { "get": { "tags": ["Results"], "summary": "Fetch async generation result", "description": "Fetch the result of a generation by ID. \n\nMake sure to use the same API key to fetch the generation result that you used to create the generation, \notherwise you will receive a `404` response.\n\n### How to use\nPlease invoke this endpoint with a `GET` request.\n\nThe headers of the request must include an API key in the `authorization` field and the ID\nof your generation must be in the path.\n\n### How is progress reported?\nYour generation is either `in-progress` (i.e. status code `202`) or it is complete (i.e. status code `200`). \nWe may add more fine-grained progress reporting in the future (e.g. a numerical progress).\n\n### How long are results stored?\nResults are stored for 24 hours after generation. After that, the results are deleted.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\ngeneration_id = \"e52772ac75b...\"\n\nresponse = requests.request(\n \"GET\",\n f\"https://api.stability.ai/v2beta/results/{generation_id}\",\n headers={\n 'accept': \"image/*\", # Use 'application/json' to receive base64 encoded JSON\n 'authorization': f\"Bearer sk-MYAPIKEY\"\n },\n)\n\nif response.status_code == 202:\n print(\"Generation in-progress, try again in 10 seconds.\")\nelif response.status_code == 200:\n print(\"Generation complete!\")\n with open(\"result.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport fs from \"node:fs\";\n\nconst generationID = \"e52772ac75b...\";\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2beta/results/${generationID}`,\n method: \"GET\",\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"image/*\", // Use 'application/json' to receive base64 encoded JSON\n },\n});\n\nif (response.status === 202) {\n console.log(\"Generation is still running, try again in 10 seconds.\");\n} else if (response.status === 200) {\n console.log(\"Generation is complete!\");\n fs.writeFileSync(\"result.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`Response ${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "generation_id=\"e52772ac75b...\"\nurl=\"https://api.stability.ai/v2beta/results/$generation_id\"\nhttp_status=$(curl -sS -f -o \"./result.webp\" -w '%{http_code}' -H \"authorization: sk-MYAPIKEY\" -H 'accept: image/*' \"$url\")\n\ncase $http_status in\n 202)\n echo \"Still processing. Retrying in 10 seconds...\"\n ;;\n 200)\n echo \"Download complete!\"\n ;;\n 4*|5*)\n mv \"./result.webp\" \"./error.json\"\n echo \"Error: Check ./error.json for details.\"\n exit 1\n ;;\nesac" } ], "parameters": [ { "schema": { "$ref": "#/components/schemas/GenerationID" }, "required": true, "name": "id", "in": "path" }, { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "default": "*/*", "description": "Specify `*/*` to receive the bytes of the result directly. Otherwise specify `application/json` to receive the result as base64 encoded JSON.", "enum": ["*/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "responses": { "200": { "description": "Generation finished.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "202": { "description": "Your generation is still in-progress.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" }, "status": { "type": "string", "enum": ["in-progress"], "description": "The status of your generation." } }, "required": ["id", "status"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "404": { "description": "id: the generation either does not exist or has expired.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2bca35116bc5431d6dc4b4ea2ef3da2f", "name": "generation_not_found", "errors": [ "id: the generation either does not exist or has expired." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/upscale/conservative": { "post": { "tags": ["Upscale"], "summary": "Conservative", "description": "Takes images between 64x64 and 1 megapixel and upscales them all the way to 4K resolution. Put more generally, it can upscale images ~20-40x times while preserving all aspects. Conservative Upscale minimizes alterations to the image and should not be used to reimagine an image.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=t1Q4w2uvvza0)\n\n### How to use\n\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n- `prompt`\n\nOptionally, the body of the request may also include:\n- `negative_prompt`\n- `seed`\n- `output_format`\n- `creativity`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 4 megapixels.\n\n### Credits\nFlat rate of 25 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/upscale/conservative\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./low-res-flower.jpg\", \"rb\"),\n },\n data={\n \"prompt\": \"a flower\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./flower.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./low-res-flower.jpg\"),\n prompt: \"a flower\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/upscale/conservative`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./flower.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/upscale/conservative\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./low-res-flower.jpg\" \\\n -F prompt=\"a flower\" \\\n -F output_format=\"webp\" \\\n -o \"./flower.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to upscale.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." }, "creativity": { "$ref": "#/components/schemas/Creativity" } }, "required": ["image", "prompt"] } } } }, "responses": { "200": { "description": "Upscale was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/upscale/creative": { "post": { "tags": ["Upscale"], "summary": "Creative Upscale (async)", "description": "Takes images between 64x64 and 1 megapixel and upscales them all the way to **4K** resolution. Put more \ngenerally, it can upscale images ~20-40x times while preserving, and often enhancing, quality. \nCreative Upscale **works best on highly degraded images and is not for photos of 1mp or above** as it performs \nheavy reimagining (controlled by creativity scale).\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=QXxi9tfI425t)\n\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n\nThe body may optionally include:\n- `seed`\n- `negative_prompt`\n- `output_format`\n- `creativity`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Results\nAfter invoking this endpoint with the required parameters, use the `id` in the response to poll for results at the\n[results/{id} endpoint](#tag/Results/paths/~1v2beta~1results~1%7Bid%7D/get). Rate-limiting or other errors may occur if you poll more than once every 10 seconds.\n\n### Credits\nFlat rate of 25 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/upscale/creative\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./kitten-in-space.png\", \"rb\")\n },\n data={\n \"prompt\": \"cute fluffy white kitten floating in space, pastel colors\",\n \"output_format\": \"webp\",\n },\n)\n\nprint(\"Generation ID:\", response.json().get('id'))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./kitten-in-space.png\"),\n prompt: \"cute fluffy white kitten floating in space, pastel colors\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/upscale/creative`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`\n },\n },\n);\n\nconsole.log(\"Generation ID:\", response.data.id);" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/upscale/creative\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -F image=@\"./kitten-in-rainforest.png\" \\\n -F prompt=\"cute fluffy white kitten sitting in a rainforest, pastel colors\" \\\n -F output_format=webp \\\n -o \"./output.json\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to upscale.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 1,048,576 pixels", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "creativity": { "type": "number", "minimum": 0, "maximum": 0.35, "default": 0.3, "description": "Indicates how creative the model should be when upscaling an image.\nHigher values will result in more details being added to the image during upscaling." } }, "required": ["image", "prompt"] } } } }, "responses": { "200": { "description": "Upscale was started.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" } }, "required": ["id"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/upscale/creative/result/{id}": { "get": { "tags": [], "summary": "Fetch Creative Upscale result", "description": "Fetch the result of an upscale generation by ID. \n\nMake sure to use the same API key to fetch the generation result that you used to create the generation, \notherwise you will receive a `404` response.\n\n### How to use\nPlease invoke this endpoint with a `GET` request.\n\nThe headers of the request must include an API key in the `authorization` field and the ID\nof your generation must be in the path.\n\n### How is progress reported?\nYour generation is either `in-progress` (i.e. status code `202`) or it is complete (i.e. status code `200`). \nWe may add more fine-grained progress reporting in the future (e.g. a numerical progress).\n\n### How long are results stored?\nResults are stored for 24 hours after generation. After that, the results are deleted.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\ngeneration_id = \"e52772ac75b...\"\n\nresponse = requests.request(\n \"GET\",\n f\"https://api.stability.ai/v2beta/stable-image/upscale/creative/result/{generation_id}\",\n headers={\n 'accept': \"image/*\", # Use 'application/json' to receive base64 encoded JSON\n 'authorization': f\"Bearer sk-MYAPIKEY\"\n },\n)\n\nif response.status_code == 202:\n print(\"Generation in-progress, try again in 10 seconds.\")\nelif response.status_code == 200:\n print(\"Generation complete!\")\n with open(\"upscaled.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport fs from \"node:fs\";\n\nconst generationID = \"e52772ac75b...\";\n\nconst response = await axios.request({\n url: `https://api.stability.ai/v2beta/stable-image/upscale/creative/result/${generationID}`,\n method: \"GET\",\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"image/*\", // Use 'application/json' to receive base64 encoded JSON\n },\n});\n\nif (response.status === 202) {\n console.log(\"Generation is still running, try again in 10 seconds.\");\n} else if (response.status === 200) {\n console.log(\"Generation is complete!\");\n fs.writeFileSync(\"upscaled.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`Response ${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "generation_id=\"e52772ac75b...\"\nurl=\"https://api.stability.ai/v2beta/stable-image/upscale/creative/result/$generation_id\"\nhttp_status=$(curl -sS -f -o \"./upscaled.webp\" -w '%{http_code}' -H \"authorization: sk-MYAPIKEY\" -H 'accept: image/*' \"$url\")\n\ncase $http_status in\n 202)\n echo \"Still processing. Retrying in 10 seconds...\"\n ;;\n 200)\n echo \"Download complete!\"\n ;;\n 4*|5*)\n mv \"./upscaled.webp\" \"./error.json\"\n echo \"Error: Check ./error.json for details.\"\n exit 1\n ;;\nesac" } ], "parameters": [ { "schema": { "$ref": "#/components/schemas/GenerationID" }, "required": true, "name": "id", "in": "path" }, { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "responses": { "200": { "description": "Upscale finished.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "202": { "description": "Your upscale generation is still in-progress.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" }, "status": { "type": "string", "enum": ["in-progress"], "description": "The status of your generation." } }, "required": ["id", "status"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "404": { "description": "id: the generation either does not exist or has expired.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2bca35116bc5431d6dc4b4ea2ef3da2f", "name": "generation_not_found", "errors": [ "id: the generation either does not exist or has expired." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/upscale/fast": { "post": { "tags": ["Upscale"], "summary": "Fast", "description": "Our Fast Upscaler service enhances image resolution by 4x using predictive and generative AI. This lightweight and fast service (processing in ~1 second) is ideal for enhancing the quality of compressed images, making it suitable for social media posts and other applications.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=t1Q4w2uvvza0)\n\n### How to use\n\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n\nOptionally, the body of the request may also include:\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image is 4 times that of the input image with a maximum size of 16 megapixels.\n\n### Credits\nFlat rate of 1 credit per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/upscale/fast\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./low-res-flower.jpg\", \"rb\"),\n },\n data={\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./flower.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./low-res-flower.jpg\"),\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/upscale/fast`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./flower.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/upscale/fast\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./low-res-flower.jpg\" \\\n -F output_format=\"webp\" \\\n -o \"./flower.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to upscale.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Width must be between 32 and 1,536 pixels\n- Height must be between 32 and 1,536 pixels\n- Total pixel count must be between 1,024 and 1,048,576 pixels", "format": "binary", "example": "./some/image.png" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image"] } } } }, "responses": { "200": { "description": "Upscale was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/erase": { "post": { "tags": ["Edit"], "summary": "Erase", "description": "The Erase service removes unwanted objects, such as blemishes on portraits or items on desks, using image masks.\n\nThe mask is provided in one of two ways:\n 1. Explicitly passing in a separate image via the `mask` parameter \n 2. Derived from the alpha channel of the `image` parameter.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=t1Q4w2uvvza0)\n\n### How to use\n\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n\nOptionally, the body of the request may also include:\n- `mask`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 4 megapixels.\n\n### Credits\nFlat rate of 3 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/erase\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./kangaroo-on-the-beach.png\", \"rb\"),\n \"mask\": open(\"./mask-of-kangaroo.png\", \"rb\"),\n },\n data={\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./just-the-beach.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./kangaroo-on-the-beach.png\"),\n mask: fs.createReadStream(\"./mask-of-kangaroo.png\"),\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/erase`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./just-the-beach.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/erase\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./kangaroo-on-the-beach.png\" \\\n -F mask=@\"./mask-of-kangaroo.png\" \\\n -F output_format=\"webp\" \\\n -o \"./just-the-beach.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to erase from.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "mask": { "type": "string", "description": "Controls the strength of the inpainting process on a per-pixel basis, either via a \nsecond image (passed into this parameter) or via the alpha channel of the `image` parameter.\n\n**Passing in a Mask** \n\nThe image passed to this parameter should be a black and white image that represents, \nat any pixel, the strength of inpainting based on how dark or light the given pixel is. \nCompletely black pixels represent no inpainting strength while completely white pixels \nrepresent maximum strength.\n\nIn the event the mask is a different size than the `image` parameter, it will be automatically resized.\n\n**Alpha Channel Support**\n\nIf you don't provide an explicit mask, one will be derived from the alpha channel of the `image` parameter.\nTransparent pixels will be inpainted while opaque pixels will be preserved.\n\nIn the event an `image` with an alpha channel is provided along with a `mask`, the `mask` will take precedence.", "format": "binary", "example": "./some/image.png" }, "grow_mask": { "type": "number", "minimum": 0, "maximum": 20, "default": 5, "description": "Grows the edges of the mask outward in all directions by the specified number of pixels. The expanded area around the mask will be blurred, which can help smooth the transition between inpainted content and the original image.\n\nTry this parameter if you notice seams or rough edges around the inpainted content.\n\n> Note: Excessive growth may obscure fine details in the mask and/or merge nearby masked regions." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt"] } } } }, "responses": { "200": { "description": "Erase was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/inpaint": { "post": { "tags": ["Edit"], "summary": "Inpaint", "description": "Intelligently modify images by filling in or replacing specified areas with new content based\non the content of a \"mask\" image. \n\nThe \"mask\" is provided in one of two ways:\n 1. Explicitly passing in a separate image via the `mask` parameter \n 2. Derived from the alpha channel of the `image` parameter.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=t1Q4w2uvvza0)\n\n### How to use\n\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n- `prompt`\n\nOptionally, the body of the request may also include:\n- `mask`\n- `negative_prompt`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 4 megapixels.\n\n### Credits\nFlat rate of 3 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/inpaint\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./dog-wearing-vr-goggles.png\", \"rb\"),\n \"mask\": open(\"./mask.png\", \"rb\"),\n },\n data={\n \"prompt\": \"dog wearing black glasses\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./dog-wearing-black-glasses.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./dog-wearing-vr-goggles.png\"),\n mask: fs.createReadStream(\"./mask.png\"),\n prompt: \"dog wearing black glasses\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/inpaint`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./dog-wearing-black-glasses.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/inpaint\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./dog-wearing-vr-goggles.png\" \\\n -F mask=@\"./mask.png\" \\\n -F prompt=\"golden retriever in a field\" \\\n -F output_format=\"webp\" \\\n -o \"./dog-wearing-black-glasses.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to inpaint.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "mask": { "type": "string", "description": "Controls the strength of the inpainting process on a per-pixel basis, either via a \nsecond image (passed into this parameter) or via the alpha channel of the `image` parameter.\n\n**Passing in a Mask** \n\nThe image passed to this parameter should be a black and white image that represents, \nat any pixel, the strength of inpainting based on how dark or light the given pixel is. \nCompletely black pixels represent no inpainting strength while completely white pixels \nrepresent maximum strength.\n\nIn the event the mask is a different size than the `image` parameter, it will be automatically resized.\n\n**Alpha Channel Support**\n\nIf you don't provide an explicit mask, one will be derived from the alpha channel of the `image` parameter.\nTransparent pixels will be inpainted while opaque pixels will be preserved.\n\nIn the event an `image` with an alpha channel is provided along with a `mask`, the `mask` will take precedence.", "format": "binary", "example": "./some/image.png" }, "grow_mask": { "type": "number", "minimum": 0, "maximum": 100, "default": 5, "description": "Grows the edges of the mask outward in all directions by the specified number of pixels. The expanded area around the mask will be blurred, which can help smooth the transition between inpainted content and the original image.\n\nTry this parameter if you notice seams or rough edges around the inpainted content.\n\n> Note: Excessive growth may obscure fine details in the mask and/or merge nearby masked regions." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt"] } } } }, "responses": { "200": { "description": "Inpainting was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/outpaint": { "post": { "tags": ["Edit"], "summary": "Outpaint", "description": "The Outpaint service inserts additional content in an image to fill in the space in any direction. \nCompared to other automated or manual attempts to expand the content in an image, the Outpaint service \nshould minimize artifacts and signs that the original image has been edited.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=bZ2yK7VQSgLw)\n\n### How to use\n\nPlease invoke this endpoint with a POST request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n\nAlong with _at least one_ outpaint direction:\n- `left`\n- `right`\n- `up`\n- `down`\n\n> **Note:** for best quality use outpaint direction values smaller or equal to your source image dimensions.\n \nEach of these parameters should be set to a number between 0 and 2000, representing the number of pixels to outpaint in that direction.\n\nOptionally, the body of the request may also include:\n- `prompt`\n- `seed`\n- `output_format`\n- `creativity`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Credits\nFlat rate of 4 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/outpaint\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./husky-in-a-field.png\", \"rb\")\n },\n data={\n \"left\": 200,\n \"down\": 200,\n \"output_format\": \"webp\"\n },\n)\n\nif response.status_code == 200:\n with open(\"./husky-in-a-huge-field.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./husky-in-a-field.png\"),\n left: 200,\n down: 200,\n output_format: \"webp\",\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/outpaint`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./husky-in-a-huge-field.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/outpaint\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./husky-in-a-field.png\" \\\n -F left=200 \\\n -F bottom=200 \\\n -F output_format=\"webp\" \\\n -o \"./husky-in-a-huge-field.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image you wish to outpaint.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "left": { "type": "integer", "minimum": 0, "maximum": 2000, "default": 0, "description": "The number of pixels to outpaint on the left side of the image. At least one outpainting direction must be supplied with a non-zero value." }, "right": { "type": "integer", "minimum": 0, "maximum": 2000, "default": 0, "description": "The number of pixels to outpaint on the right side of the image. At least one outpainting direction must be supplied with a non-zero value." }, "up": { "type": "integer", "minimum": 0, "maximum": 2000, "default": 0, "description": "The number of pixels to outpaint on the top of the image. At least one outpainting direction must be supplied with a non-zero value." }, "down": { "type": "integer", "minimum": 0, "maximum": 2000, "default": 0, "description": "The number of pixels to outpaint on the bottom of the image. At least one outpainting direction must be supplied with a non-zero value." }, "creativity": { "allOf": [ { "$ref": "#/components/schemas/Creativity" }, { "minimum": 0, "maximum": 1, "default": 0.5 } ] }, "prompt": { "type": "string", "minLength": 0, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image"] } } } }, "responses": { "200": { "description": "Outpainting was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/search-and-replace": { "post": { "tags": ["Edit"], "summary": "Search and Replace", "description": "The Search and Replace service is a specific version of inpainting that does not require a mask. \nInstead, users can leverage a `search_prompt` to identify an object in simple language to be replaced. \nThe service will automatically segment the object and replace it with the object requested in the prompt.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=0lDpGa2jAmAs)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n- `search_prompt`\n\nThe body may optionally include:\n- `seed`\n- `negative_prompt`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 4 megapixels.\n\n### Credits\nFlat rate of 4 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/search-and-replace\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./husky-in-a-field.png\", \"rb\")\n },\n data={\n \"prompt\": \"golden retriever in a field\",\n \"search_prompt\": \"dog\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./golden-retriever-in-a-field.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./husky-in-a-field.png\"),\n prompt: \"golden retriever standing in a field\",\n search_prompt: \"dog\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/search-and-replace`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\"\n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./golden-retriever-in-a-field.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/search-and-replace\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./husky-in-a-field.png\" \\\n -F prompt=\"golden retriever in a field\" \\\n -F search_prompt=\"dog\" \\\n -F output_format=\"webp\" \\\n -o \"./golden-retriever-in-a-field.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "An image containing content you wish to replace.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "search_prompt": { "type": "string", "maxLength": 10000, "description": "Short description of what to inpaint in the `image`.", "example": "glasses" }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "grow_mask": { "type": "number", "minimum": 0, "maximum": 20, "default": 3, "description": "Grows the edges of the mask outward in all directions by the specified number of pixels. The expanded area around the mask will be blurred, which can help smooth the transition between inpainted content and the original image.\n\nTry this parameter if you notice seams or rough edges around the inpainted content.\n\n> Note: Excessive growth may obscure fine details in the mask and/or merge nearby masked regions." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt", "search_prompt"] } } } }, "responses": { "200": { "description": "Search-and-Replace was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/search-and-recolor": { "post": { "tags": ["Edit"], "summary": "Search and Recolor", "description": "The Search and Recolor service provides the ability to change the color of a specific object in an image using a prompt.\nThis service is a specific version of inpainting that does not require a mask. The Search and Recolor \nservice will automatically segment the object and recolor it using the colors requested in the prompt.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=mtgSh4Stj3l)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n- `select_prompt`\n\nThe body may optionally include:\n- `grow_mask`\n- `seed`\n- `negative_prompt`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will match the resolution of the input image.\n\n### Credits\nFlat rate of 5 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/search-and-recolor\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./red-car.png\", \"rb\")\n },\n data={\n \"prompt\": \"a yellow car\",\n \"select_prompt\": \"car\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./yellow-car.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./red-car.png\"),\n prompt: \"a yellow car\",\n select_prompt: \"car\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/search-and-recolor`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\"\n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./yellow-car.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/search-and-recolor\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./red-car.png\" \\\n -F prompt=\"a yellow car\" \\\n -F select_prompt=\"car\" \\\n -F output_format=\"webp\" \\\n -o \"./yellow-car.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "An image containing content you wish to recolor.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "select_prompt": { "type": "string", "maxLength": 10000, "description": "Short description of what to search for in the `image`.", "example": "glasses" }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "grow_mask": { "type": "number", "minimum": 0, "maximum": 20, "default": 3, "description": "Grows the edges of the mask outward in all directions by the specified number of pixels. The expanded area around the mask will be blurred, which can help smooth the transition between inpainted content and the original image.\n\nTry this parameter if you notice seams or rough edges around the inpainted content.\n\n> Note: Excessive growth may obscure fine details in the mask and/or merge nearby masked regions." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt", "select_prompt"] } } } }, "responses": { "200": { "description": "Search-and-Recolor was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/remove-background": { "post": { "tags": ["Edit"], "summary": "Remove Background", "description": "The Remove Background service accurately segments the foreground from an image and implements \nand removes the background.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=VHofb3LAVmqi)\n\n\n### How to use\n\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n \nThe body of the request must include:\n- `image`\n\nOptionally, the body of the request may also include:\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Credits\nFlat rate of 2 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/remove-background\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./husky-in-a-field.png\", \"rb\")\n },\n data={\n \"output_format\": \"webp\"\n },\n)\n\nif response.status_code == 200:\n with open(\"./husky.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n image: fs.createReadStream(\"./husky-in-a-field.png\"),\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/remove-background`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./husky.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/remove-background\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./husky-in-a-field.png\" \\\n -F output_format=\"webp\" \\\n -o \"./husky.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The image whose background you wish to remove.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 4,194,304 pixels", "format": "binary", "example": "./some/image.png" }, "output_format": { "type": "string", "enum": ["png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image"] } } } }, "responses": { "200": { "description": "Background successfully removed.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/edit/replace-background-and-relight": { "post": { "tags": ["Edit"], "summary": "Replace Background and Relight (async)", "description": "The Replace Background and Relight edit service lets users swap backgrounds with\nAI-generated or uploaded images while adjusting lighting to match the subject. This\nnew API provides a streamlined image editing solution and can serve e-commerce, real\nestate, photography, and creative projects.\n\nSome of the things you can do include:\n - Background Replacement: Remove existing background and add new ones.\n - AI Background Generation: Create new backgrounds using AI generated images based on prompts.\n - Relighting: Adjust lighting in images that are under or overexposed.\n - Flexible Inputs: Use your own background image or generate one.\n - Lighting Adjustments: Modify light reference, direction, and strength.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=mtgSh4Stj3l)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`.\n\nThe body of the request should include:\n- `subject_image`\n- `background_prompt` and/or `background_reference`\n\nThe body may optionally include:\n- `light_reference` or `light_source_direction`\n- `light_source_strength` (requires `light_reference` or `light_source_direction`)\n- `foreground_prompt`\n- `negative_prompt`\n- `preserve_original_subject`\n- `original_background_depth`\n- `keep_original_background`\n- `light_source_strength`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Results\nAfter invoking this endpoint with the required parameters, use the `id` in the response to poll for results at the\n[results/{id} endpoint](#tag/Results/paths/~1v2beta~1results~1%7Bid%7D/get). Rate-limiting or other errors may occur if you poll more than once every 10 seconds.\n\n### Credits\nFlat rate of 8 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/edit/replace-background-and-relight\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"subject_image\": open(\"./husky-in-a-field.png\", \"rb\")\n },\n data={\n \"background_prompt\": \"cinematic lighting\",\n \"output_format\": \"webp\",\n },\n)\n\nprint(\"Generation ID:\", response.json().get('id'))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n subject_image: fs.createReadStream(\"./husky-in-a-field.png\"),\n background_prompt: \"cinematic lighting\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/edit/replace-background-and-relight`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n },\n },\n);\n\nconsole.log(\"Generation ID:\", response.data.id);" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/edit/replace-background-and-relight\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F subject_image=@\"./husky-in-a-field.png\" \\\n -F background_prompt=\"cinematic lighting\" \\\n -F output_format=\"webp\" \\\n -o \"./output.json\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "subject_image": { "type": "string", "description": "An image containing the subject that you wish to change background and relight.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "background_reference": { "type": "string", "description": "An image whose style you wish to use in the background. Similar to the Control: Style API,\nstylistic elements from this image are added to the background.\n\n> **Important:** either `background_reference` or `background_prompt` must be provided.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "background_prompt": { "type": "string", "maxLength": 10000, "description": "What you wish to see in the background of the output image. This could be a description\nof the desired background scene, or just a description of the lighting if modifying the\nlight source through `light_source_direction` or `light_reference`.\n\n> **Important:** either `background_reference` or `background_prompt` must be provided." }, "foreground_prompt": { "type": "string", "maxLength": 10000, "description": "Description of the subject. Use this to prevent elements of the background from\nbleeding into the subject. For example, if you find your subject is turning \ngreen with a forest in the background, try putting a short description of the \nsubject in this field." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "preserve_original_subject": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.6, "description": "How much to overlay the original subject to exactly match the original image. A \n1.0 is an exact pixel match for the subject, and 0.0 is a close match but will \nhave new lighting qualities. This is an advanced feature." }, "original_background_depth": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.5, "description": "Controls the generated background to have the same depth as the original subject image. This is an advanced feature." }, "keep_original_background": { "type": "string", "enum": ["true", "false"], "default": "false", "description": "Whether to keep the background of the original image. When this is on, the background\nwill have different lighting than the original image that changes based on the other\nparameters in this API." }, "light_source_direction": { "type": "string", "enum": ["left", "right", "above", "below"], "description": "Direction of the light source." }, "light_reference": { "type": "string", "description": "An image with the desired lighting. Lighter sections of the light_reference image will correspond to sections with brighter lighting in the output image.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "light_source_strength": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.3, "description": "If using `light_reference_image` or `light_source_direction`, controls the strength \nof the light source. 1.0 is brighter and 0.0 is dimmer. This is an advanced feature.\n\n> **Important:** Use of this parameter requires `light_reference` or `light_source_direction` to be provided." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["subject_image"] } } } }, "responses": { "200": { "description": "Replace Background and Relight was started.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/GenerationID" } }, "required": ["id"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/generate/ultra": { "post": { "tags": ["Generate"], "summary": "Stable Image Ultra", "description": "Our most advanced text to image generation service, Stable Image Ultra creates the highest quality images\nwith unprecedented prompt understanding. Ultra excels in typography, complex compositions, dynamic lighting, \nvibrant hues, and overall cohesion and structure of an art piece. Made from the most advanced models,\nincluding Stable Diffusion 3.5, Ultra offers the best of the Stable Diffusion ecosystem.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=yXhs626oZdr1)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`. The accept header should be set to one of the following:\n- `image/*` to receive the image in the format specified by the `output_format` parameter.\n- `application/json` to receive the image in the format specified by the `output_format` parameter, but encoded to base64 in a JSON response.\n\nThe only required parameter is the `prompt` field, which should contain the text prompt for the image generation.\n\nThe body of the request should include:\n- `prompt` - text to generate the image from\n\nThe body may optionally include:\n- `image` - the image to use as the starting point for the generation\n- `strength` - controls how much influence the `image` parameter has on the output image\n- `aspect_ratio` - the aspect ratio of the output image\n- `negative_prompt` - keywords of what you **do not** wish to see in the output image\n- `seed` - the randomness seed to use for the generation\n- `output_format` - the the format of the output image\n\n> **Note:** for the full list of optional parameters, please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 1 megapixel. The default resolution is 1024x1024.\n\n### Credits\nThe Ultra service uses 8 credits per successful result. You will not be charged for failed results.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/generate/ultra\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\"none\": ''},\n data={\n \"prompt\": \"Lighthouse on a cliff overlooking the ocean\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./lighthouse.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n prompt: \"Lighthouse on a cliff overlooking the ocean\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/generate/ultra`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./lighthouse.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/generate/ultra\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F prompt=\"Lighthouse on a cliff overlooking the ocean\" \\\n -F output_format=\"webp\" \\\n -o \"./lighthouse.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "aspect_ratio": { "type": "string", "enum": [ "21:9", "16:9", "3:2", "5:4", "1:1", "4:5", "2:3", "9:16", "9:21" ], "default": "1:1", "description": "Controls the aspect ratio of the generated image." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." }, "image": { "type": "string", "description": "The image to use as the starting point for the generation.\n\n> **Important:** The `strength` parameter is required when `image` is provided.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Width must be between 64 and 16,384 pixels\n- Height must be between 64 and 16,384 pixels\n- Total pixel count must be at least 4,096 pixels", "format": "binary", "example": "./some/image.png" }, "strength": { "type": "number", "minimum": 0, "maximum": 1, "description": "Sometimes referred to as _denoising_, this parameter controls how much influence the \n`image` parameter has on the generated image. A value of 0 would yield an image that \nis identical to the input. A value of 1 would be as if you passed in no image at all.\n\n> **Important:** This parameter is required when `image` is provided." } }, "required": ["prompt"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/generate/core": { "post": { "tags": ["Generate"], "summary": "Stable Image Core", "description": "Our primary service for text-to-image generation, Stable Image Core represents the best quality achievable at high \nspeed. No prompt engineering is required! Try asking for a style, a scene, or a character, and see what you get.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=yXhs626oZdr1)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `prompt`\n\nThe body may optionally include:\n- `aspect_ratio`\n- `negative_prompt`\n- `seed`\n- `style_preset`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 1.5 megapixels.\n\n### Credits\nFlat rate of 3 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/generate/core\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\"none\": ''},\n data={\n \"prompt\": \"Lighthouse on a cliff overlooking the ocean\",\n \"output_format\": \"webp\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./lighthouse.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n prompt: \"Lighthouse on a cliff overlooking the ocean\",\n output_format: \"webp\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/generate/core`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./lighthouse.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/generate/core\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F prompt=\"Lighthouse on a cliff overlooking the ocean\" \\\n -F output_format=\"webp\" \\\n -o \"./lighthouse.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "aspect_ratio": { "type": "string", "enum": [ "21:9", "16:9", "3:2", "5:4", "1:1", "4:5", "2:3", "9:16", "9:21" ], "default": "1:1", "description": "Controls the aspect ratio of the generated image." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "style_preset": { "type": "string", "enum": [ "enhance", "anime", "photographic", "digital-art", "comic-book", "fantasy-art", "line-art", "analog-film", "neon-punk", "isometric", "low-poly", "origami", "modeling-compound", "cinematic", "3d-model", "pixel-art", "tile-texture" ], "description": "Guides the image model towards a particular style." }, "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["prompt"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/generate/sd3": { "post": { "tags": ["Generate"], "summary": "Stable Diffusion 3.0 & 3.5", "description": "Generate using Stable Diffusion 3.5 models, Stability AI latest base model:\n\n- **Stable Diffusion 3.5 Large**: At 8 billion parameters, with superior quality and\n prompt adherence, this base model is the most powerful in the Stable Diffusion\n family. This model is ideal for professional use cases at 1 megapixel resolution.\n\n- **Stable Diffusion 3.5 Large Turbo**: A distilled version of Stable Diffusion 3.5 Large.\n SD3.5 Large Turbo generates high-quality images with exceptional prompt adherence\n in just 4 steps, making it considerably faster than Stable Diffusion 3.5 Large.\n\n- **Stable Diffusion 3.5 Medium**: With 2.5 billion parameters, the model delivers an\n optimal balance between prompt accuracy and image quality, making it an efficient\n choice for fast high-performance image generation.\n\nRead more about the model capabilities [here](https://stability.ai/news/introducing-stable-diffusion-3-5).\n\nStable Diffusion 3.0 models are also supported, powered by [Fireworks AI](https://fireworks.ai/). API status can be reviewed [here](https://readme.fireworks.ai/page/application-status).\n\n- **SD3 Large**: the 8 billion parameter model\n- **SD3 Large Turbo**: the 8 billion parameter model with a faster inference time\n- **SD3 Medium**: the 2 billion parameter model\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/SD3_API.ipynb)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`. The accept header should be set to one of the following:\n- `image/*` to receive the image in the format specified by the `output_format` parameter.\n- `application/json` to receive the image encoded as base64 in a JSON response.\n\n#### **Generating with a prompt**\nCommonly referred to as **text-to-image**, this mode generates an image from text alone. While the only required\nparameter is the `prompt`, it also supports an `aspect_ratio` parameter which can be used to control the\naspect ratio of the generated image.\n\n#### **Generating with a prompt *and* an image**\nCommonly referred to as **image-to-image**, this mode also generates an image from text but uses an existing image as the\nstarting point. The required parameters are:\n- `prompt` - text to generate the image from\n- `image` - the image to use as the starting point for the generation\n- `strength` - controls how much influence the `image` parameter has on the output image\n- `mode` - must be set to `image-to-image`\n\n> **Note:** maximum request size is 10MiB.\n\n#### **Optional Parameters:**\nBoth modes support the following optional parameters:\n- `model` - the model to use (SD3 Large, SD3 Large Turbo, or SD3 Medium)\n- `output_format` - the the format of the output image\n- `seed` - the randomness seed to use for the generation\n- `negative_prompt` - keywords of what you **do not** wish to see in the output image\n- `cfg_scale` - controls how strictly the diffusion process adheres to the prompt text\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 1MP. The default resolution is 1024x1024.\n\n### Credits\n- **SD 3.5 & 3.0 Large**: Flat rate of 6.5 credits per successful generation.\n- **SD 3.5 & 3.0 Large Turbo**: Flat rate of 4 credits per successful generation.\n- **SD 3.5 & 3.0 Medium**: Flat rate of 3.5 credits per successful generation.\n\nAs always, you will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/generate/sd3\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\"none\": ''},\n data={\n \"prompt\": \"Lighthouse on a cliff overlooking the ocean\",\n \"output_format\": \"jpeg\",\n },\n)\n\nif response.status_code == 200:\n with open(\"./lighthouse.jpeg\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import fs from \"node:fs\";\nimport axios from \"axios\";\nimport FormData from \"form-data\";\n\nconst payload = {\n prompt: \"Lighthouse on a cliff overlooking the ocean\",\n output_format: \"jpeg\"\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/generate/sd3`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: { \n Authorization: `Bearer sk-MYAPIKEY`, \n Accept: \"image/*\" \n },\n },\n);\n\nif(response.status === 200) {\n fs.writeFileSync(\"./lighthouse.jpeg\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/generate/sd3\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F prompt=\"Lighthouse on a cliff overlooking the ocean\" \\\n -F output_format=\"jpeg\" \\\n -o \"./lighthouse.jpeg\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines\nelements, colors, and subjects will lead to better results." }, "mode": { "type": "string", "enum": ["text-to-image", "image-to-image"], "default": "text-to-image", "description": "Controls whether this is a text-to-image or image-to-image generation, which affects which parameters are required:\n- **text-to-image** requires only the `prompt` parameter\n- **image-to-image** requires the `prompt`, `image`, and `strength` parameters", "title": "GenerationMode" }, "image": { "type": "string", "description": "The image to use as the starting point for the generation.\n\nSupported formats:\n - jpeg\n - png\n - webp\n\nSupported dimensions:\n - Every side must be at least 64 pixels\n \n> **Important:** This parameter is only valid for **image-to-image** requests.", "format": "binary" }, "strength": { "type": "number", "minimum": 0, "maximum": 1, "description": "Sometimes referred to as _denoising_, this parameter controls how much influence the \n`image` parameter has on the generated image. A value of 0 would yield an image that \nis identical to the input. A value of 1 would be as if you passed in no image at all.\n\n> **Important:** This parameter is only valid for **image-to-image** requests." }, "aspect_ratio": { "type": "string", "enum": [ "21:9", "16:9", "3:2", "5:4", "1:1", "4:5", "2:3", "9:16", "9:21" ], "default": "1:1", "description": "Controls the aspect ratio of the generated image. Defaults to 1:1.\n\n> **Important:** This parameter is only valid for **text-to-image** requests." }, "model": { "type": "string", "enum": [ "sd3.5-large", "sd3.5-large-turbo", "sd3.5-medium", "sd3-medium", "sd3-large", "sd3-large-turbo" ], "default": "sd3.5-large", "description": "The model to use for generation.\n\n- `sd3.5-large` requires 6.5 credits per generation\n- `sd3.5-large-turbo` requires 4 credits per generation\n- `sd3.5-medium` requires 3.5 credits per generation\n- `sd3-large` requires 6.5 credits per generation\n- `sd3-large-turbo` requires 4 credits per generation\n- `sd3-medium` requires 3.5 credits per generation" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["png", "jpeg"], "default": "png", "description": "Dictates the `content-type` of the generated image." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "Keywords of what you **do not** wish to see in the output image.\nThis is an advanced feature.\n\n> **Important:** This parameter does **not** work with `sd3-large-turbo`." }, "cfg_scale": { "type": "number", "minimum": 1, "maximum": 10, "description": "How strictly the diffusion process adheres to the prompt text (higher values keep your image closer to your prompt)." } }, "required": ["prompt"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/control/sketch": { "post": { "tags": ["Control"], "summary": "Sketch", "description": "This service offers an ideal solution for design projects that require brainstorming and\nfrequent iterations. It upgrades rough hand-drawn sketches to refined outputs with precise \ncontrol. For non-sketch images, it allows detailed manipulation of the final appearance by \nleveraging the contour lines and edges within the image.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=ZKIAqHzJzzUo)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n\nThe body may optionally include:\n- `control_strength`\n- `negative_prompt`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will match that of the input image.\n\n### Credits\nFlat rate of 3 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/control/sketch\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./sketch.png\", \"rb\")\n },\n data={\n \"prompt\": \"a medieval castle on a hill\",\n \"control_strength\": 0.7,\n \"output_format\": \"webp\"\n },\n)\n\nif response.status_code == 200:\n with open(\"./castle.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport FormData from \"form-data\";\nimport fs from \"node:fs\";\n\nconst payload = {\n image: fs.createReadStream(\"./sketch.png\"),\n prompt: \"a medieval castle on a hill\",\n control_strength: 0.6,\n output_format: \"webp\",\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/control/sketch`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"image/*\"\n },\n },\n);\n\nif (response.status === 200) {\n fs.writeFileSync(\"./castle.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/control/sketch\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./sketch.png\" \\\n -F prompt=\"a medieval castle on a hill\" \\\n -F control_strength=0.7 \\\n -F output_format=\"webp\" \\\n -o \"./castle.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "image": { "type": "string", "description": "Supported Formats:\n- jpeg\n- png\n- webp\n\nImage Dimensions:\n- Every side must be at least 64 pixels\n- The total pixel count cannot exceed 9,437,184 pixels (e.g. 3072x3072, 4096x2304, etc.)\n\nImage Aspect Ratio:\n- Must be between 1:2.5 and 2.5:1 (i.e. cannot be too tall or too wide)", "format": "binary", "example": "./some/image.png" }, "control_strength": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.7, "description": "How much influence, or control, the `image` has on the generation. Represented as a float between 0 and 1, where 0 is the least influence and 1 is the maximum." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["prompt", "image"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/control/structure": { "post": { "tags": ["Control"], "summary": "Structure", "description": "This service excels in generating images by maintaining the structure of an input image, \nmaking it especially valuable for advanced content creation scenarios such as recreating \nscenes or rendering characters from models.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=59RaZazXz0AU)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n\nThe body may optionally include:\n- `control_strength`\n- `negative_prompt`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will match that of the input image.\n\n### Credits\nFlat rate of 3 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/control/structure\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./cat-statue.png\", \"rb\")\n },\n data={\n \"prompt\": \"a well manicured shrub in an english garden\",\n \"control_strength\": 0.7,\n \"output_format\": \"webp\"\n },\n)\n\nif response.status_code == 200:\n with open(\"./shrub-in-a-garden.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport FormData from \"form-data\";\nimport fs from \"node:fs\";\n\nconst payload = {\n image: fs.createReadStream(\"./cat-statue.png\"),\n prompt: \"a well manicured shrub in an english garden\",\n control_strength: 0.6,\n output_format: \"webp\",\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/control/structure`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"image/*\"\n },\n },\n);\n\nif (response.status === 200) {\n fs.writeFileSync(\"./shrub-in-a-garden.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/control/structure\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./cat-statue.png\" \\\n -F prompt=\"a well manicured shrub in an english garden\" \\\n -F control_strength=0.7 \\\n -F output_format=\"webp\" \\\n -o \"./shrub-in-a-garden.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "image": { "type": "string", "description": "An image whose structure you wish to use as the foundation for a generation.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "control_strength": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.7, "description": "How much influence, or control, the `image` has on the generation. Represented as a float between 0 and 1, where 0 is the least influence and 1 is the maximum." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["prompt", "image"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v2beta/stable-image/control/style": { "post": { "tags": ["Control"], "summary": "Style", "description": "This service extracts stylistic elements from an input image (control image) and uses it to guide the creation of an output image based on the prompt. The result is a new image in the same style as the control image.\n\n### Try it out\nGrab your [API key](https://platform.stability.ai/account/keys) and head over to [![Open Google Colab](https://platform.stability.ai/svg/google-colab.svg)](https://colab.research.google.com/github/stability-ai/stability-sdk/blob/main/nbs/Stable_Image_API_Public.ipynb#scrollTo=y0WKjG72RvTE)\n\n### How to use\nPlease invoke this endpoint with a `POST` request.\n\nThe headers of the request must include an API key in the `authorization` field. The body of the request must be\n`multipart/form-data`, and the `accept` header should be set to one of the following:\n - `image/*` to receive the image in the format specified by the `output_format` parameter.\n - `application/json` to receive the image encoded as base64 in a JSON response.\n\nThe body of the request should include:\n- `image`\n- `prompt`\n\nThe body may optionally include:\n- `negative_prompt`\n- `aspect_ratio`\n- `fidelity`\n- `seed`\n- `output_format`\n\n> **Note:** for more details about these parameters please see the request schema below.\n\n### Output\nThe resolution of the generated image will be 1MP. The default resolution is 1024x1024.\n\n### Credits\nFlat rate of 4 credits per successful generation. You will not be charged for failed generations.", "x-codeSamples": [ { "lang": "python", "label": "Python", "source": "import requests\n\nresponse = requests.post(\n f\"https://api.stability.ai/v2beta/stable-image/control/style\",\n headers={\n \"authorization\": f\"Bearer sk-MYAPIKEY\",\n \"accept\": \"image/*\"\n },\n files={\n \"image\": open(\"./cinematic-portrait.png\", \"rb\")\n },\n data={\n \"prompt\": \"a majestic portrait of a chicken\",\n \"output_format\": \"webp\"\n },\n)\n\nif response.status_code == 200:\n with open(\"./chicken-portrait.webp\", 'wb') as file:\n file.write(response.content)\nelse:\n raise Exception(str(response.json()))" }, { "lang": "javascript", "label": "JavaScript", "source": "import axios from \"axios\";\nimport FormData from \"form-data\";\nimport fs from \"node:fs\";\n\nconst payload = {\n image: fs.createReadStream(\"./cinematic-portrait.png\"),\n prompt: \"a majestic portrait of a chicken\",\n output_format: \"webp\",\n};\n\nconst response = await axios.postForm(\n `https://api.stability.ai/v2beta/stable-image/control/style`,\n axios.toFormData(payload, new FormData()),\n {\n validateStatus: undefined,\n responseType: \"arraybuffer\",\n headers: {\n Authorization: `Bearer sk-MYAPIKEY`,\n Accept: \"image/*\"\n },\n },\n);\n\nif (response.status === 200) {\n fs.writeFileSync(\"./chicken-portrait.webp\", Buffer.from(response.data));\n} else {\n throw new Error(`${response.status}: ${response.data.toString()}`);\n}" }, { "lang": "terminal", "label": "cURL", "source": "curl -f -sS \"https://api.stability.ai/v2beta/stable-image/control/style\" \\\n -H \"authorization: Bearer sk-MYAPIKEY\" \\\n -H \"accept: image/*\" \\\n -F image=@\"./cinematic-portrait.png\" \\\n -F prompt=\"a majestic portrait of a chicken\" \\\n -F output_format=\"webp\" \\\n -o \"./chicken-portrait.webp\"" } ], "parameters": [ { "schema": { "type": "string", "description": "Your [Stability API key](https://platform.stability.ai/account/keys), used to authenticate your requests. Although you may have multiple keys in your account, you should use the same key for all requests to this API.", "minLength": 1 }, "required": true, "name": "authorization", "in": "header" }, { "schema": { "type": "string", "minLength": 1, "description": "The content type of the request body. Do not manually specify this header; your HTTP client library will automatically include the appropriate boundary parameter.", "example": "multipart/form-data" }, "required": true, "name": "content-type", "in": "header" }, { "schema": { "type": "string", "default": "image/*", "description": "Specify `image/*` to receive the bytes of the image directly. Otherwise specify `application/json` to receive the image as base64 encoded JSON.", "enum": ["image/*", "application/json"] }, "required": false, "name": "accept", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientID" }, "required": false, "name": "stability-client-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientUserID" }, "required": false, "name": "stability-client-user-id", "in": "header" }, { "schema": { "$ref": "#/components/schemas/StabilityClientVersion" }, "required": false, "name": "stability-client-version", "in": "header" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "image": { "type": "string", "description": "An image whose style you wish to use as the foundation for a generation.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels\n- The aspect ratio must be between 1:2.5 and 2.5:1", "format": "binary", "example": "./some/image.png" }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "aspect_ratio": { "type": "string", "enum": [ "21:9", "16:9", "3:2", "5:4", "1:1", "4:5", "2:3", "9:16", "9:21" ], "default": "1:1", "description": "Controls the aspect ratio of the generated image." }, "fidelity": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.5, "description": "How closely the output image's style resembles the input image's style." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["prompt", "image"] } } } }, "responses": { "200": { "description": "Generation was successful.", "headers": { "x-request-id": { "description": "A unique identifier for this request.", "schema": { "type": "string" } }, "content-type": { "description": "The format of the generated image.\n\n To receive the bytes of the image directly, specify `image/*` in the accept header. To receive the bytes base64 encoded inside of a JSON payload, specify `application/json`.", "examples": { "png": { "description": "raw bytes", "value": "image/png" }, "pngJSON": { "description": "base64 encoded", "value": "application/json; type=image/png" }, "jpeg": { "description": "raw bytes", "value": "image/jpeg" }, "jpegJSON": { "description": "base64 encoded", "value": "application/json; type=image/jpeg" }, "webp": { "description": "raw bytes", "value": "image/webp" }, "webpJSON": { "description": "base64 encoded", "value": "application/json; type=image/webp" } }, "schema": { "type": "string" } }, "finish-reason": { "schema": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"] }, "description": "Indicates the reason the generation finished. \n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `finish_reason`." }, "seed": { "description": "The seed used as random noise for this generation.\n\n> **NOTE:** This header is absent on JSON encoded responses because it is present in the body as `seed`.", "example": "343940597", "schema": { "type": "string" } } }, "content": { "image/png": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated png.\n(Caution: may contain cats)" }, "application/json; type=image/png": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/jpeg": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated jpeg.\n(Caution: may contain cats)" }, "application/json; type=image/jpeg": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } }, "image/webp": { "schema": { "type": "string", "description": "The bytes of the generated image.\n\nThe `finish-reason` and `seed` will be present as headers.", "format": "binary" }, "example": "The bytes of the generated webp.\n(Caution: may contain cats)" }, "application/json; type=image/webp": { "schema": { "type": "object", "properties": { "image": { "type": "string", "description": "The generated image, encoded to base64.", "example": "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1..." }, "finish_reason": { "type": "string", "enum": ["SUCCESS", "CONTENT_FILTERED"], "description": "The reason the generation finished.\n\n- `SUCCESS` = successful generation.\n- `CONTENT_FILTERED` = successful generation, however the output violated our content moderation \npolicy and has been blurred as a result.", "example": "SUCCESS" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "The seed used as random noise for this generation.", "example": 343940597 } }, "required": ["image", "finish_reason"] } } } }, "400": { "description": "Invalid parameter(s), see the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] } } } }, "403": { "description": "Your request was flagged by our content moderation system.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentModerationResponse" } } } }, "413": { "description": "Your request was larger than 10MiB.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "4212a4b66fbe1cedca4bf2133d35dca5", "name": "payload_too_large", "errors": [ "body: payloads cannot be larger than 10MiB in size" ] } } } } }, "422": { "description": "Your request was well-formed, but rejected. See the `errors` field for details.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"] }, "examples": { "Invalid Language": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "invalid_language", "errors": [ "English is the only supported language for this service." ] } }, "Public Figure Detected": { "value": { "id": "ff54b236a3acdde1522cb1ba641c43ed", "name": "public_figure", "errors": [ "Our system detected the likeness of a public figure in your image. To comply with our guidelines, this request cannot be processed. Please upload a different image." ] } } } } } }, "429": { "description": "You have made more than 150 requests in 10 seconds.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "rate_limit_exceeded", "name": "rate_limit_exceeded", "errors": [ "You have exceeded the rate limit of 150 requests within a 10 second period, and have been timed out for 60 seconds." ] } } } } }, "500": { "description": "An internal error occurred. If the problem persists [contact support](https://stabilityplatform.freshdesk.com/support/tickets/new).", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Short-hand name for an error, useful for discriminating between errors with the same status code.", "example": "bad_request" }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "example": { "id": "2a1b2d4eafe2bc6ab4cd4d5c6133f513", "name": "internal_error", "errors": [ "An unexpected server error has occurred, please try again later." ] } } } } } } } }, "/v1/generation/{engine_id}/text-to-image": { "post": { "description": "Generate an image from a text prompt. \n### Using SDXL 1.0\nUse `stable-diffusion-xl-1024-v1-0` as the `engine_id` of your request and pass in `height` & `width` as one of the following combinations:\n- 1024x1024 (default)\n- 1152x896\n- 896x1152\n- 1216x832\n- 1344x768\n- 768x1344\n- 1536x640\n- 640x1536 \n\n### SDXL 1.0 Pricing\nWhen specifying 30 steps or fewer, generation costs 0.9 credits.\n\nWhen specifying above 30 steps, generation cost is determiend using the following formula:\n\n `cost = 0.9 * (steps / 30)`\n\n### Using SD 1.6\nSD1.6 is a flexible-resolution base model allowing you to generate non-standard aspect ratios. The model is optimized for a resolution of 512 x 512 pixels. To generate 1 megapixel outputs, we recommend using SDXL 1.0, which is available at the same price.\n\nPass in `stable-diffusion-v1-6` as the `engine_id` of your request and ensure the `height` & `width` you pass in adhere to the following restrictions:\n- No dimension can be less than 320 pixels\n- No dimension can be greater than 1536 pixels\n- Height and width must be specified in increments of 64\n- The default resolution is 512 x 512\n", "operationId": "textToImage", "summary": "Text-to-image", "tags": ["SDXL 1.0 & SD1.6"], "parameters": [ { "$ref": "#/components/parameters/engineID" }, { "$ref": "#/components/parameters/accept" }, { "$ref": "#/components/parameters/organization" }, { "$ref": "#/components/parameters/stabilityClientID" }, { "$ref": "#/components/parameters/stabilityClientVersion" } ], "requestBody": { "content": { "application/json": { "example": { "cfg_scale": 7, "height": 512, "width": 512, "sampler": "K_DPM_2_ANCESTRAL", "samples": 1, "steps": 30, "text_prompts": [ { "text": "A lighthouse on a cliff", "weight": 1 } ] }, "schema": { "$ref": "#/components/schemas/TextToImageRequestBody" } } }, "required": true }, "responses": { "200": { "$ref": "#/components/responses/GenerationResponse" }, "400": { "$ref": "#/components/responses/400FromGeneration" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import base64\nimport os\nimport requests\n\nengine_id = \"stable-diffusion-v1-6\"\napi_host = os.getenv('API_HOST', 'https://api.stability.ai')\napi_key = os.getenv(\"STABILITY_API_KEY\")\n\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.post(\n f\"{api_host}/v1/generation/{engine_id}/text-to-image\",\n headers={\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n \"Authorization\": f\"Bearer {api_key}\"\n },\n json={\n \"text_prompts\": [\n {\n \"text\": \"A lighthouse on a cliff\"\n }\n ],\n \"cfg_scale\": 7,\n \"height\": 1024,\n \"width\": 1024,\n \"samples\": 1,\n \"steps\": 30,\n },\n)\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\ndata = response.json()\n\nfor i, image in enumerate(data[\"artifacts\"]):\n with open(f\"./out/v1_txt2img_{i}.png\", \"wb\") as f:\n f.write(base64.b64decode(image[\"base64\"]))\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\nimport fs from 'node:fs'\n\nconst engineId = 'stable-diffusion-v1-6'\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst apiKey = process.env.STABILITY_API_KEY\n\nif (!apiKey) throw new Error('Missing Stability API key.')\n\nconst response = await fetch(\n `${apiHost}/v1/generation/${engineId}/text-to-image`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n text_prompts: [\n {\n text: 'A lighthouse on a cliff',\n },\n ],\n cfg_scale: 7,\n height: 1024,\n width: 1024,\n steps: 30,\n samples: 1,\n }),\n }\n)\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface GenerationResponse {\n artifacts: Array<{\n base64: string\n seed: number\n finishReason: string\n }>\n}\n\nconst responseJSON = (await response.json()) as GenerationResponse\n\nresponseJSON.artifacts.forEach((image, index) => {\n fs.writeFileSync(\n `./out/v1_txt2img_${index}.png`,\n Buffer.from(image.base64, 'base64')\n )\n})\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype TextToImageImage struct {\n\tBase64 string `json:\"base64\"`\n\tSeed uint32 `json:\"seed\"`\n\tFinishReason string `json:\"finishReason\"`\n}\n\ntype TextToImageResponse struct {\n\tImages []TextToImageImage `json:\"artifacts\"`\n}\n\nfunc main() {\n\t// Build REST endpoint URL w/ specified engine\n\tengineId := \"stable-diffusion-v1-6\"\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/generation/\" + engineId + \"/text-to-image\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\tvar data = []byte(`{\n\t\t\"text_prompts\": [\n\t\t {\n\t\t\t\"text\": \"A lighthouse on a cliff\"\n\t\t }\n\t\t],\n\t\t\"cfg_scale\": 7,\n\t\t\"height\": 1024,\n\t\t\"width\": 1024,\n\t\t\"samples\": 1,\n\t\t\"steps\": 30\n \t}`)\n\n\treq, _ := http.NewRequest(\"POST\", reqUrl, bytes.NewBuffer(data))\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\n\t// Execute the request & read all the bytes of the body\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\n\tif res.StatusCode != 200 {\n\t\tvar body map[string]interface{}\n\t\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpanic(fmt.Sprintf(\"Non-200 response: %s\", body))\n\t}\n\n\t// Decode the JSON body\n\tvar body TextToImageResponse\n\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Write the images to disk\n\tfor i, image := range body.Images {\n\t\toutFile := fmt.Sprintf(\"./out/v1_txt2img_%d.png\", i)\n\t\tfile, err := os.Create(outFile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\timageBytes, err := base64.StdEncoding.DecodeString(image.Base64)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif _, err := file.Write(imageBytes); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif err := file.Close(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n" }, { "lang": "cURL", "source": "if [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\nOUTPUT_FILE=./out/v1_txt2img.png\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/generation/stable-diffusion-v1-6/text-to-image\"\n\ncurl -f -sS -X POST \"$URL\" \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: image/png' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\" \\\n --data-raw '{\n \"text_prompts\": [\n {\n \"text\": \"A lighthouse on a cliff\"\n }\n ],\n \"cfg_scale\": 7,\n \"height\": 1024,\n \"width\": 1024,\n \"samples\": 1,\n \"steps\": 30\n }' \\\n -o \"$OUTPUT_FILE\"\n" } ] } }, "/v1/generation/{engine_id}/image-to-image": { "post": { "description": "Produce an image from an existing image using a text prompt. \n### How to control strength of generation\nTo preserve only roughly 35% of the initial image, pass in either `init_image_mode=IMAGE_STRENGTH` and `image_strength=0.35` or `init_image_mode=STEP_SCHEDULE` and `step_schedule_start=0.65`. Both of these are equivalent, however `init_image_mode=STEP_SCHEDULE` also lets you pass in `step_schedule_end`, which can provide an extra level of control for those who need it. For more details, see the specific fields below. \n\n> NOTE: Only **Version 1** engines will work with this endpoint.", "operationId": "imageToImage", "summary": "Image-to-image with prompt", "tags": ["SDXL 1.0 & SD1.6"], "parameters": [ { "$ref": "#/components/parameters/engineID" }, { "$ref": "#/components/parameters/accept" }, { "$ref": "#/components/parameters/organization" }, { "$ref": "#/components/parameters/stabilityClientID" }, { "$ref": "#/components/parameters/stabilityClientVersion" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/ImageToImageRequestBody" }, "examples": { "IMAGE_STRENGTH": { "summary": "Using IMAGE_STRENGTH", "description": "Request using 35% image_strength", "value": { "image_strength": 0.35, "init_image_mode": "IMAGE_STRENGTH", "init_image": "", "text_prompts[0][text]": "A dog space commander", "text_prompts[0][weight]": 1, "cfg_scale": 7, "sampler": "K_DPM_2_ANCESTRAL", "samples": 3, "steps": 30 } }, "STEP_SCHEDULE": { "summary": "Using STEP_SCHEDULE", "description": "Equivalent request using step_schedule_start", "value": { "step_schedule_start": 0.65, "init_image_mode": "STEP_SCHEDULE", "init_image": "", "text_prompts[0][text]": "A dog space commander", "text_prompts[0][weight]": 1, "cfg_scale": 7, "sampler": "K_DPM_2_ANCESTRAL", "samples": 3, "steps": 30 } } } } }, "required": true }, "responses": { "200": { "$ref": "#/components/responses/GenerationResponse" }, "400": { "$ref": "#/components/responses/400FromGeneration" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import base64\nimport os\nimport requests\n\nengine_id = \"stable-diffusion-v1-6\"\napi_host = os.getenv(\"API_HOST\", \"https://api.stability.ai\")\napi_key = os.getenv(\"STABILITY_API_KEY\")\n\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.post(\n f\"{api_host}/v1/generation/{engine_id}/image-to-image\",\n headers={\n \"Accept\": \"application/json\",\n \"Authorization\": f\"Bearer {api_key}\"\n },\n files={\n \"init_image\": open(\"../init_image.png\", \"rb\")\n },\n data={\n \"image_strength\": 0.35,\n \"init_image_mode\": \"IMAGE_STRENGTH\",\n \"text_prompts[0][text]\": \"Galactic dog with a cape\",\n \"cfg_scale\": 7,\n \"samples\": 1,\n \"steps\": 30,\n }\n)\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\ndata = response.json()\n\nfor i, image in enumerate(data[\"artifacts\"]):\n with open(f\"./out/v1_img2img_{i}.png\", \"wb\") as f:\n f.write(base64.b64decode(image[\"base64\"]))\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\nimport FormData from 'form-data'\nimport fs from 'node:fs'\n\nconst engineId = 'stable-diffusion-v1-6'\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst apiKey = process.env.STABILITY_API_KEY\n\nif (!apiKey) throw new Error('Missing Stability API key.')\n\n// NOTE: This example is using a NodeJS FormData library.\n// Browsers should use their native FormData class.\n// React Native apps should also use their native FormData class.\nconst formData = new FormData()\nformData.append('init_image', fs.readFileSync('../init_image.png'))\nformData.append('init_image_mode', 'IMAGE_STRENGTH')\nformData.append('image_strength', 0.35)\nformData.append('text_prompts[0][text]', 'Galactic dog wearing a cape')\nformData.append('cfg_scale', 7)\nformData.append('samples', 1)\nformData.append('steps', 30)\n\nconst response = await fetch(\n `${apiHost}/v1/generation/${engineId}/image-to-image`,\n {\n method: 'POST',\n headers: {\n ...formData.getHeaders(),\n Accept: 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: formData,\n }\n)\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface GenerationResponse {\n artifacts: Array<{\n base64: string\n seed: number\n finishReason: string\n }>\n}\n\nconst responseJSON = (await response.json()) as GenerationResponse\n\nresponseJSON.artifacts.forEach((image, index) => {\n fs.writeFileSync(\n `out/v1_img2img_${index}.png`,\n Buffer.from(image.base64, 'base64')\n )\n})\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype ImageToImageImage struct {\n\tBase64 string `json:\"base64\"`\n\tSeed uint32 `json:\"seed\"`\n\tFinishReason string `json:\"finishReason\"`\n}\n\ntype ImageToImageResponse struct {\n\tImages []ImageToImageImage `json:\"artifacts\"`\n}\n\nfunc main() {\n\tengineId := \"stable-diffusion-v1-6\"\n\n\t// Build REST endpoint URL\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/generation/\" + engineId + \"/image-to-image\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\tdata := &bytes.Buffer{}\n\twriter := multipart.NewWriter(data)\n\n\t// Write the init image to the request\n\tinitImageWriter, _ := writer.CreateFormField(\"init_image\")\n\tinitImageFile, initImageErr := os.Open(\"../init_image.png\")\n\tif initImageErr != nil {\n\t\tpanic(\"Could not open init_image.png\")\n\t}\n\t_, _ = io.Copy(initImageWriter, initImageFile)\n\n\t// Write the options to the request\n\t_ = writer.WriteField(\"init_image_mode\", \"IMAGE_STRENGTH\")\n\t_ = writer.WriteField(\"image_strength\", \"0.35\")\n\t_ = writer.WriteField(\"text_prompts[0][text]\", \"Galactic dog with a cape\")\n\t_ = writer.WriteField(\"cfg_scale\", \"7\")\n\t_ = writer.WriteField(\"samples\", \"1\")\n\t_ = writer.WriteField(\"steps\", \"30\")\n\twriter.Close()\n\n\t// Execute the request\n\tpayload := bytes.NewReader(data.Bytes())\n\treq, _ := http.NewRequest(\"POST\", reqUrl, payload)\n\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\n\tif res.StatusCode != 200 {\n\t\tvar body map[string]interface{}\n\t\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpanic(fmt.Sprintf(\"Non-200 response: %s\", body))\n\t}\n\n\t// Decode the JSON body\n\tvar body ImageToImageResponse\n\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Write the images to disk\n\tfor i, image := range body.Images {\n\t\toutFile := fmt.Sprintf(\"./out/v1_img2img_%d.png\", i)\n\t\tfile, err := os.Create(outFile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\timageBytes, err := base64.StdEncoding.DecodeString(image.Base64)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif _, err := file.Write(imageBytes); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif err := file.Close(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n" }, { "lang": "cURL", "source": "if [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\nOUTPUT_FILE=./out/v1_img2img.png\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/generation/stable-diffusion-v1-6/image-to-image\"\n\ncurl -f -sS -X POST \"$URL\" \\\n -H 'Content-Type: multipart/form-data' \\\n -H 'Accept: image/png' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\" \\\n -F 'init_image=@\"../init_image.png\"' \\\n -F 'init_image_mode=IMAGE_STRENGTH' \\\n -F 'image_strength=0.35' \\\n -F 'text_prompts[0][text]=A galactic dog in space' \\\n -F 'cfg_scale=7' \\\n -F 'samples=1' \\\n -F 'steps=30' \\\n -o \"$OUTPUT_FILE\"\n" } ] } }, "/v1/generation/{engine_id}/image-to-image/masking": { "post": { "description": "Selectively modify portions of an image using a mask. The `mask` must be the same shape and size as the init image. This endpoint also supports `image` parameters with alpha channels. See below for more details. \n\n> NOTE: Only **Version 1** engines will work with this endpoint.", "operationId": "masking", "summary": "Image-to-image with a mask", "tags": ["SDXL 1.0 & SD1.6"], "parameters": [ { "example": "stable-diffusion-xl-1024-v1-0", "in": "path", "name": "engine_id", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/accept" }, { "$ref": "#/components/parameters/organization" }, { "$ref": "#/components/parameters/stabilityClientID" }, { "$ref": "#/components/parameters/stabilityClientVersion" } ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/MaskingRequestBody" }, "examples": { "MASK_IMAGE_BLACK": { "value": { "mask_source": "MASK_IMAGE_BLACK", "init_image": "", "mask_image": "", "text_prompts[0][text]": "A dog space commander", "text_prompts[0][weight]": 1, "cfg_scale": 7, "sampler": "K_DPM_2_ANCESTRAL", "samples": 3, "steps": 30 } }, "MASK_IMAGE_WHITE": { "value": { "mask_source": "MASK_IMAGE_WHITE", "init_image": "", "mask_image": "", "text_prompts[0][text]": "A dog space commander", "text_prompts[0][weight]": 1, "cfg_scale": 7, "sampler": "K_DPM_2_ANCESTRAL", "samples": 3, "steps": 30 } }, "INIT_IMAGE_ALPHA": { "value": { "mask_source": "INIT_IMAGE_ALPHA", "init_image": "", "text_prompts[0][text]": "A dog space commander", "text_prompts[0][weight]": 1, "cfg_scale": 7, "sampler": "K_DPM_2_ANCESTRAL", "samples": 3, "steps": 30 } } } } } }, "responses": { "200": { "$ref": "#/components/responses/GenerationResponse" }, "400": { "$ref": "#/components/responses/400FromGeneration" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import base64\nimport os\nimport requests\n\nengine_id = \"stable-diffusion-v1-6\"\napi_host = os.getenv('API_HOST', 'https://api.stability.ai')\napi_key = os.getenv(\"STABILITY_API_KEY\")\n\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.post(\n f\"{api_host}/v1/generation/{engine_id}/image-to-image/masking\",\n headers={\n \"Accept\": 'application/json',\n \"Authorization\": f\"Bearer {api_key}\"\n },\n files={\n 'init_image': open(\"../init_image.png\", 'rb'),\n 'mask_image': open(\"../mask_image_black.png\", 'rb'),\n },\n data={\n \"mask_source\": \"MASK_IMAGE_BLACK\",\n \"text_prompts[0][text]\": \"A large spiral galaxy with a bright central bulge and a ring of stars around it\",\n \"cfg_scale\": 7,\n \"clip_guidance_preset\": \"FAST_BLUE\",\n \"samples\": 1,\n \"steps\": 30,\n }\n)\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\ndata = response.json()\n\nfor i, image in enumerate(data[\"artifacts\"]):\n with open(f\"./out/v1_img2img_masking_{i}.png\", \"wb\") as f:\n f.write(base64.b64decode(image[\"base64\"]))\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\nimport FormData from 'form-data'\nimport fs from 'node:fs'\n\nconst engineId = 'stable-diffusion-v1-6'\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst apiKey = process.env.STABILITY_API_KEY\n\nif (!apiKey) throw new Error('Missing Stability API key.')\n\n// NOTE: This example is using a NodeJS FormData library. Browser\n// implementations should use their native FormData class. React Native\n// implementations should also use their native FormData class.\nconst formData = new FormData()\nformData.append('init_image', fs.readFileSync('../init_image.png'))\nformData.append('mask_image', fs.readFileSync('../mask_image_black.png'))\nformData.append('mask_source', 'MASK_IMAGE_BLACK')\nformData.append(\n 'text_prompts[0][text]',\n 'A large spiral galaxy with a bright central bulge and a ring of stars around it'\n)\nformData.append('cfg_scale', '7')\nformData.append('clip_guidance_preset', 'FAST_BLUE')\nformData.append('samples', 1)\nformData.append('steps', 30)\n\nconst response = await fetch(\n `${apiHost}/v1/generation/${engineId}/image-to-image/masking`,\n {\n method: 'POST',\n headers: {\n ...formData.getHeaders(),\n Accept: 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: formData,\n }\n)\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface GenerationResponse {\n artifacts: Array<{\n base64: string\n seed: number\n finishReason: string\n }>\n}\n\nconst responseJSON = (await response.json()) as GenerationResponse\n\nresponseJSON.artifacts.forEach((image, index) => {\n fs.writeFileSync(\n `out/v1_img2img_masking_${index}.png`,\n Buffer.from(image.base64, 'base64')\n )\n})\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype MaskingImage struct {\n\tBase64 string `json:\"base64\"`\n\tSeed uint32 `json:\"seed\"`\n\tFinishReason string `json:\"finishReason\"`\n}\n\ntype MaskingResponse struct {\n\tImages []MaskingImage `json:\"artifacts\"`\n}\n\nfunc main() {\n\tengineId := \"stable-diffusion-v1-6\"\n\n\t// Build REST endpoint URL\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/generation/\" + engineId + \"/image-to-image/masking\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\tdata := &bytes.Buffer{}\n\twriter := multipart.NewWriter(data)\n\n\t// Write the init image to the request\n\tinitImageWriter, _ := writer.CreateFormField(\"init_image\")\n\tinitImageFile, initImageErr := os.Open(\"../init_image.png\")\n\tif initImageErr != nil {\n\t\tpanic(\"Could not open init_image.png\")\n\t}\n\t_, _ = io.Copy(initImageWriter, initImageFile)\n\n\t// Write the mask image to the request\n\tmaskImageWriter, _ := writer.CreateFormField(\"mask_image\")\n\tmaskImageFile, maskImageErr := os.Open(\"../mask_image_black.png\")\n\tif maskImageErr != nil {\n\t\tpanic(\"Could not open mask_image_white.png\")\n\t}\n\t_, _ = io.Copy(maskImageWriter, maskImageFile)\n\n\t// Write the options to the request\n\t_ = writer.WriteField(\"mask_source\", \"MASK_IMAGE_BLACK\")\n\t_ = writer.WriteField(\"text_prompts[0][text]\", \"A large spiral galaxy with a bright central bulge and a ring of stars around it\")\n\t_ = writer.WriteField(\"cfg_scale\", \"7\")\n\t_ = writer.WriteField(\"clip_guidance_preset\", \"FAST_BLUE\")\n\t_ = writer.WriteField(\"samples\", \"1\")\n\t_ = writer.WriteField(\"steps\", \"30\")\n\twriter.Close()\n\n\t// Execute the request & read all the bytes of the response\n\tpayload := bytes.NewReader(data.Bytes())\n\treq, _ := http.NewRequest(\"POST\", reqUrl, payload)\n\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\n\tif res.StatusCode != 200 {\n\t\tvar body map[string]interface{}\n\t\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpanic(fmt.Sprintf(\"Non-200 response: %s\", body))\n\t}\n\n\t// Decode the JSON body\n\tvar body MaskingResponse\n\tif err := json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Write the images to disk\n\tfor i, image := range body.Images {\n\t\toutFile := fmt.Sprintf(\"./out/v1_img2img_masking_%d.png\", i)\n\t\tfile, err := os.Create(outFile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\timageBytes, err := base64.StdEncoding.DecodeString(image.Base64)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif _, err := file.Write(imageBytes); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif err := file.Close(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n" }, { "lang": "cURL", "source": "#!/bin/sh\n\nset -e\n\nif [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\nOUTPUT_FILE=./out/v1_img2img_masking.png\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/generation/stable-diffusion-v1-6/image-to-image/masking\"\n\ncurl -f -sS -X POST \"$URL\" \\\n -H 'Content-Type: multipart/form-data' \\\n -H 'Accept: image/png' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\" \\\n -F 'init_image=@\"../init_image.png\"' \\\n -F 'mask_image=@\"../mask_image_black.png\"' \\\n -F 'mask_source=MASK_IMAGE_BLACK' \\\n -F 'text_prompts[0][text]=A large spiral galaxy with a bright central bulge and a ring of stars around it' \\\n -F 'cfg_scale=7' \\\n -F 'clip_guidance_preset=FAST_BLUE' \\\n -F 'samples=1' \\\n -F 'steps=30' \\\n -o \"$OUTPUT_FILE\"\n" } ] } }, "/v1/engines/list": { "get": { "description": "List all engines available to your organization/user", "operationId": "listEngines", "summary": "List engines", "tags": ["Engines"], "parameters": [ { "$ref": "#/components/parameters/organization" }, { "$ref": "#/components/parameters/stabilityClientID" }, { "$ref": "#/components/parameters/stabilityClientVersion" } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ListEnginesResponseBody" } } }, "description": "OK response." }, "401": { "$ref": "#/components/responses/401" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import os\nimport requests\n\napi_host = os.getenv('API_HOST', 'https://api.stability.ai')\nurl = f\"{api_host}/v1/engines/list\"\n\napi_key = os.getenv(\"STABILITY_API_KEY\")\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.get(url, headers={\n \"Authorization\": f\"Bearer {api_key}\"\n})\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\n# Do something with the payload...\npayload = response.json()\n\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\n\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst url = `${apiHost}/v1/engines/list`\n\nconst apiKey = process.env.STABILITY_API_KEY\nif (!apiKey) throw new Error('Missing Stability API key.')\n\nconst response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n})\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface Payload {\n engines: Array<{\n id: string\n name: string\n description: string\n type: string\n }>\n}\n\n// Do something with the payload...\nconst payload = (await response.json()) as Payload\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Build REST endpoint URL\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/engines/list\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\t// Execute the request & read all the bytes of the response\n\treq, _ := http.NewRequest(\"GET\", reqUrl, nil)\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\tbody, _ := io.ReadAll(res.Body)\n\n\tif res.StatusCode != 200 {\n\t\tpanic(\"Non-200 response: \" + string(body))\n\t}\n\n\t// Do something with the payload...\n\t// payload := string(body)\n}\n" }, { "lang": "cURL", "source": "if [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/engines/list\"\n\ncurl -f -sS \"$URL\" \\\n -H 'Accept: application/json' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\"\n" } ] } }, "/v1/user/account": { "get": { "description": "Get information about the account associated with the provided API key", "operationId": "userAccount", "summary": "Account details", "tags": ["User"], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountResponseBody" } } }, "description": "OK response." }, "401": { "$ref": "#/components/responses/401" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import os\nimport requests\n\napi_host = os.getenv('API_HOST', 'https://api.stability.ai')\nurl = f\"{api_host}/v1/user/account\"\n\napi_key = os.getenv(\"STABILITY_API_KEY\")\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.get(url, headers={\n \"Authorization\": f\"Bearer {api_key}\"\n})\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\n# Do something with the payload...\npayload = response.json()\n\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\n\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst url = `${apiHost}/v1/user/account`\n\nconst apiKey = process.env.STABILITY_API_KEY\nif (!apiKey) throw new Error('Missing Stability API key.')\n\nconst response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n})\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface User {\n id: string\n profile_picture: string\n email: string\n organizations?: Array<{\n id: string\n name: string\n role: string\n is_default: boolean\n }>\n}\n\n// Do something with the user...\nconst user = (await response.json()) as User\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Build REST endpoint URL\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/user/account\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\t// Build the request\n\treq, _ := http.NewRequest(\"GET\", reqUrl, nil)\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\n\t// Execute the request\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\tbody, _ := io.ReadAll(res.Body)\n\n\tif res.StatusCode != 200 {\n\t\tpanic(\"Non-200 response: \" + string(body))\n\t}\n\n\t// Do something with the payload...\n\t// payload := string(body)\n}\n" }, { "lang": "cURL", "source": "if [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\n# Determine the URL to use for the request\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/user/account\"\n\ncurl -f -sS \"$URL\" \\\n -H 'Accept: application/json' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\"\n" } ] } }, "/v1/user/balance": { "get": { "description": "Get the credit balance of the account/organization associated with the API key", "operationId": "userBalance", "summary": "Account balance", "tags": ["User"], "parameters": [ { "$ref": "#/components/parameters/organization" }, { "$ref": "#/components/parameters/stabilityClientID" }, { "$ref": "#/components/parameters/stabilityClientVersion" } ], "responses": { "200": { "content": { "application/json": { "example": { "credits": 0.6336833840314097 }, "schema": { "$ref": "#/components/schemas/BalanceResponseBody" } } }, "description": "OK response." }, "401": { "$ref": "#/components/responses/401" }, "500": { "$ref": "#/components/responses/500" } }, "security": [ { "STABILITY_API_KEY": [] } ], "x-codeSamples": [ { "lang": "Python", "source": "import os\nimport requests\n\napi_host = os.getenv('API_HOST', 'https://api.stability.ai')\nurl = f\"{api_host}/v1/user/balance\"\n\napi_key = os.getenv(\"STABILITY_API_KEY\")\nif api_key is None:\n raise Exception(\"Missing Stability API key.\")\n\nresponse = requests.get(url, headers={\n \"Authorization\": f\"Bearer {api_key}\"\n})\n\nif response.status_code != 200:\n raise Exception(\"Non-200 response: \" + str(response.text))\n\n# Do something with the payload...\npayload = response.json()\n\n" }, { "label": "TypeScript", "lang": "Javascript", "source": "import fetch from 'node-fetch'\n\nconst apiHost = process.env.API_HOST ?? 'https://api.stability.ai'\nconst url = `${apiHost}/v1/user/balance`\n\nconst apiKey = process.env.STABILITY_API_KEY\nif (!apiKey) throw new Error('Missing Stability API key.')\n\nconst response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n})\n\nif (!response.ok) {\n throw new Error(`Non-200 response: ${await response.text()}`)\n}\n\ninterface Balance {\n credits: number\n}\n\n// Do something with the balance...\nconst balance = (await response.json()) as Balance\n" }, { "lang": "Go", "source": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Build REST endpoint URL\n\tapiHost, hasApiHost := os.LookupEnv(\"API_HOST\")\n\tif !hasApiHost {\n\t\tapiHost = \"https://api.stability.ai\"\n\t}\n\treqUrl := apiHost + \"/v1/user/balance\"\n\n\t// Acquire an API key from the environment\n\tapiKey, hasAPIKey := os.LookupEnv(\"STABILITY_API_KEY\")\n\tif !hasAPIKey {\n\t\tpanic(\"Missing STABILITY_API_KEY environment variable\")\n\t}\n\n\t// Build the request\n\treq, _ := http.NewRequest(\"GET\", reqUrl, nil)\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\n\t// Execute the request\n\tres, _ := http.DefaultClient.Do(req)\n\tdefer res.Body.Close()\n\tbody, _ := io.ReadAll(res.Body)\n\n\tif res.StatusCode != 200 {\n\t\tpanic(\"Non-200 response: \" + string(body))\n\t}\n\n\t// Do something with the payload...\n\t// payload := string(body)\n}\n" }, { "lang": "cURL", "source": "if [ -z \"$STABILITY_API_KEY\" ]; then\n echo \"STABILITY_API_KEY environment variable is not set\"\n exit 1\nfi\n\n# Determine the URL to use for the request\nBASE_URL=${API_HOST:-https://api.stability.ai}\nURL=\"$BASE_URL/v1/user/balance\"\n\ncurl -f -sS \"$URL\" \\\n -H 'Content-Type: application/json' \\\n -H \"Authorization: Bearer $STABILITY_API_KEY\"\n" } ] } } }, "components": { "schemas": { "GenerationID": { "type": "string", "minLength": 64, "maxLength": 64, "description": "The `id` of a generation, typically used for async generations, that can be used to check the status of the generation or retrieve the result.", "example": "a6dc6c6e20acda010fe14d71f180658f2896ed9b4ec25aa99a6ff06c796987c4" }, "ImageToVideoRequest": { "type": "object", "properties": { "image": { "type": "string", "description": "The source image used in the video generation process.\n\nSupported Formats:\n- jpeg\n- png\n\nSupported Dimensions:\n- 1024x576\n- 576x1024\n- 768x768", "format": "binary", "example": "./some/image.png" }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "cfg_scale": { "type": "number", "minimum": 0, "maximum": 10, "default": 1.8, "description": "How strongly the video sticks to the original image. Use lower values to allow the model more freedom to make changes and higher values to correct motion distortions." }, "motion_bucket_id": { "type": "number", "minimum": 1, "maximum": 255, "default": 127, "description": "Lower values generally result in less motion in the output video, while higher values generally result in more motion. This parameter corresponds to the motion_bucket_id parameter from the [paper](https://static1.squarespace.com/static/6213c340453c3f502425776e/t/655ce779b9d47d342a93c890/1700587395994/stable_video_diffusion.pdf)." } }, "required": ["image"] }, "ContentModerationResponse": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "A unique identifier associated with this error. Please include this in any [support tickets](https://stabilityplatform.freshdesk.com/support/tickets/new) \nyou file, as it will greatly assist us in diagnosing the root cause of the problem.", "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }, "name": { "type": "string", "minLength": 1, "description": "Our content moderation system has flagged some part of your request and subsequently denied it. You were not charged for this request. While this may at times be frustrating, it is necessary to maintain the integrity of our platform and ensure a safe experience for all users.\n\nIf you would like to provide feedback, please use the [Support Form](https://stabilityplatform.freshdesk.com/support/tickets/new).", "enum": ["content_moderation"] }, "errors": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "One or more error messages indicating what went wrong.", "example": ["some-field: is required"] } }, "required": ["id", "name", "errors"], "description": "Your request was flagged by our content moderation system.", "example": { "id": "ed14db44362126aab3cbd25cca51ffe3", "name": "content_moderation", "errors": [ "Your request was flagged by our content moderation system, as a result your request was denied and you were not charged." ] } }, "InpaintingSearchModeRequestBody": { "type": "object", "properties": { "mode": { "type": "string", "enum": ["search"], "description": "Controls how the model decides which areas to inpaint and which areas to leave alone. \n\nSpecifying `mask` requires:\n - Provide an explicit mask image in the `mask` parameter\n - Use the alpha channel of the `image` parameter as the mask\n \nSpecifying `search` requires:\n - Provide a small description of what to inpaint in the `search_prompt` parameter" }, "search_prompt": { "type": "string", "description": "Short description of what to inpaint in the `image`.", "example": "glasses" }, "image": { "type": "string", "description": "The image you wish to inpaint.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt", "mode", "search_prompt"] }, "InpaintingMaskingModeRequestBody": { "type": "object", "properties": { "mode": { "type": "string", "enum": ["mask"], "description": "Controls how the model decides which areas to inpaint and which areas to leave alone. \n\nSpecifying `mask` requires:\n - Provide an explicit mask image in the `mask` parameter\n - Use the alpha channel of the `image` parameter as the mask\n \nSpecifying `search` requires:\n - Provide a small description of what to inpaint in the `search_prompt` parameter" }, "mask": { "type": "string", "description": "Controls the strength of the inpainting process on a per-pixel basis, either via a \nsecond image (passed into this parameter) or via the alpha channel of the `image` parameter.\n\n**Passing in a Mask** \n\nThe image passed to this parameter should be a black and white image that represents, \nat any pixel, the strength of inpainting based on how dark or light the given pixel is. \nCompletely black pixels represent no inpainting strength while completely white pixels \nrepresent maximum strength.\n\nIn the event the mask is a different size than the `image` parameter, it will be automatically resized.\n\n**Alpha Channel Support**\n\nIf you don't provide an explicit mask, one will be derived from the alpha channel of the `image` parameter.\nTransparent pixels will be inpainted while opaque pixels will be preserved.\n\nIn the event an `image` with an alpha channel is provided along with a `mask`, the `mask` will take precedence.", "format": "binary", "example": "./some/image.png" }, "image": { "type": "string", "description": "The image you wish to inpaint.\n\nSupported Formats:\n- jpeg\n- png\n- webp\n\nValidation Rules:\n- Every side must be at least 64 pixels\n- Total pixel count must be between 4,096 and 9,437,184 pixels", "format": "binary", "example": "./some/image.png" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 10000, "description": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines \nelements, colors, and subjects will lead to better results. \n\nTo control the weight of a given word use the format `(word:weight)`, \nwhere `word` is the word you'd like to control the weight of and `weight` \nis a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`\nwould convey a sky that was blue and green, but more green than blue." }, "negative_prompt": { "type": "string", "maxLength": 10000, "description": "A blurb of text describing what you **do not** wish to see in the output image. \nThis is an advanced feature." }, "seed": { "type": "number", "minimum": 0, "maximum": 4294967294, "default": 0, "description": "A specific value that is used to guide the 'randomness' of the generation. (Omit this parameter or pass `0` to use a random seed.)" }, "output_format": { "type": "string", "enum": ["jpeg", "png", "webp"], "default": "png", "description": "Dictates the `content-type` of the generated image." } }, "required": ["image", "prompt", "mode"] }, "StabilityClientID": { "type": "string", "maxLength": 256, "description": "The name of your application, used to help us communicate app-specific debugging or moderation issues to you.", "example": "my-awesome-app" }, "StabilityClientUserID": { "type": "string", "maxLength": 256, "description": "A unique identifier for your end user. Used to help us communicate user-specific debugging or moderation issues to you. Feel free to obfuscate this value to protect user privacy.", "example": "DiscordUser#9999" }, "StabilityClientVersion": { "type": "string", "maxLength": 256, "description": "The version of your application, used to help us communicate version-specific debugging or moderation issues to you.", "example": "1.2.1" }, "Creativity": { "type": "number", "minimum": 0.2, "maximum": 0.5, "default": 0.35, "description": "Controls the likelihood of creating additional details not heavily conditioned by the init image." }, "Engine": { "type": "object", "properties": { "description": { "type": "string" }, "id": { "type": "string", "x-go-name": "ID", "description": "Unique identifier for the engine", "example": "stable-diffusion-v1-6" }, "name": { "type": "string", "description": "Name of the engine", "example": "Stable Diffusion XL v1.0" }, "type": { "type": "string", "description": "The type of content this engine produces", "example": "PICTURE", "enum": [ "AUDIO", "CLASSIFICATION", "PICTURE", "STORAGE", "TEXT", "VIDEO" ] } }, "required": ["id", "name", "description", "type"] }, "Error": { "type": "object", "x-go-name": "RESTError", "properties": { "id": { "x-go-name": "ID", "type": "string", "description": "A unique identifier for this particular occurrence of the problem.", "example": "296a972f-666a-44a1-a3df-c9c28a1f56c0" }, "name": { "type": "string", "description": "The short-name of this class of errors e.g. `bad_request`.", "example": "bad_request" }, "message": { "type": "string", "description": "A human-readable explanation specific to this occurrence of the problem.", "example": "Header parameter Authorization is required, but not found" } }, "required": ["name", "id", "message", "status"] }, "CfgScale": { "type": "number", "description": "How strictly the diffusion process adheres to the prompt text (higher values keep your image closer to your prompt)", "default": 7, "example": 7, "minimum": 0, "maximum": 35 }, "ClipGuidancePreset": { "type": "string", "default": "NONE", "example": "FAST_BLUE", "enum": [ "FAST_BLUE", "FAST_GREEN", "NONE", "SIMPLE", "SLOW", "SLOWER", "SLOWEST" ] }, "UpscaleImageHeight": { "x-go-type": "uint64", "type": "integer", "description": "Desired height of the output image. Only one of `width` or `height` may be specified.", "minimum": 512 }, "UpscaleImageWidth": { "x-go-type": "uint64", "type": "integer", "description": "Desired width of the output image. Only one of `width` or `height` may be specified.", "minimum": 512 }, "DiffuseImageHeight": { "x-go-type": "uint64", "type": "integer", "description": "Height of the image to generate, in pixels, in an increment divisible by 64.", "multipleOf": 64, "default": 512, "example": 512, "minimum": 128 }, "DiffuseImageWidth": { "x-go-type": "uint64", "type": "integer", "description": "Width of the image to generate, in pixels, in an increment divisible by 64.", "multipleOf": 64, "default": 512, "example": 512, "minimum": 128 }, "Sampler": { "type": "string", "description": "Which sampler to use for the diffusion process. If this value is omitted we'll automatically select an appropriate sampler for you.", "example": "K_DPM_2_ANCESTRAL", "enum": [ "DDIM", "DDPM", "K_DPMPP_2M", "K_DPMPP_2S_ANCESTRAL", "K_DPM_2", "K_DPM_2_ANCESTRAL", "K_EULER", "K_EULER_ANCESTRAL", "K_HEUN", "K_LMS" ] }, "Samples": { "x-go-type": "uint64", "type": "integer", "description": "Number of images to generate", "default": 1, "example": 1, "minimum": 1, "maximum": 10 }, "Seed": { "type": "integer", "x-go-type": "uint32", "description": "Random noise seed (omit this option or use `0` for a random seed)", "default": 0, "example": 0, "minimum": 0, "maximum": 4294967295 }, "Steps": { "x-go-type": "uint64", "type": "integer", "description": "Number of diffusion steps to run.", "default": 30, "example": 50, "minimum": 10, "maximum": 50 }, "Extras": { "type": "object", "description": "Extra parameters passed to the engine.\nThese parameters are used for in-development or experimental features and may change\nwithout warning, so please use with caution." }, "StylePreset": { "type": "string", "enum": [ "enhance", "anime", "photographic", "digital-art", "comic-book", "fantasy-art", "line-art", "analog-film", "neon-punk", "isometric", "low-poly", "origami", "modeling-compound", "cinematic", "3d-model", "pixel-art", "tile-texture" ], "description": "Pass in a style preset to guide the image model towards a particular style.\nThis list of style presets is subject to change." }, "TextPrompt": { "type": "object", "properties": { "text": { "type": "string", "description": "The prompt itself", "example": "A lighthouse on a cliff", "maxLength": 2000 }, "weight": { "type": "number", "description": "Weight of the prompt (use negative numbers for negative prompts)", "example": 0.8167237, "format": "float" } }, "description": "Text prompt for image generation", "required": ["text"] }, "TextPromptsForTextToImage": { "title": "TextPrompts", "type": "array", "items": { "$ref": "#/components/schemas/TextPrompt" }, "minItems": 1, "description": "An array of text prompts to use for generation.\n\nGiven a text prompt with the text `A lighthouse on a cliff` and a weight of `0.5`, it would be represented as:\n\n```\n\"text_prompts\": [\n {\n \"text\": \"A lighthouse on a cliff\",\n \"weight\": 0.5\n }\n]\n```" }, "TextPrompts": { "description": "An array of text prompts to use for generation.\n\nDue to how arrays are represented in `multipart/form-data` requests, prompts must adhere to the format `text_prompts[index][text|weight]`,\nwhere `index` is some integer used to tie the text and weight together. While `index` does not have to be sequential, duplicate entries \nwill override previous entries, so it is recommended to use sequential indices.\n\nGiven a text prompt with the text `A lighthouse on a cliff` and a weight of `0.5`, it would be represented as:\n```\ntext_prompts[0][text]: \"A lighthouse on a cliff\"\ntext_prompts[0][weight]: 0.5\n```\n\nTo add another prompt to that request simply provide the values under a new `index`:\n\n```\ntext_prompts[0][text]: \"A lighthouse on a cliff\"\ntext_prompts[0][weight]: 0.5\ntext_prompts[1][text]: \"land, ground, dirt, grass\"\ntext_prompts[1][weight]: -0.9\n```", "type": "array", "items": { "$ref": "#/components/schemas/TextPrompt" }, "minItems": 1 }, "InputImage": { "x-go-type": "[]byte", "type": "string", "description": "The image to upscale using ESRGAN.", "example": "", "format": "binary" }, "InitImage": { "x-go-type": "[]byte", "type": "string", "description": "Image used to initialize the diffusion process, in lieu of random noise.", "example": "", "format": "binary" }, "InitImageStrength": { "type": "number", "description": "How much influence the `init_image` has on the diffusion process. Values close to `1` will yield images very similar to the `init_image` while values close to `0` will yield images wildly different than the `init_image`. The behavior of this is meant to mirror DreamStudio's \"Image Strength\" slider.

This parameter is just an alternate way to set `step_schedule_start`, which is done via the calculation `1 - image_strength`. For example, passing in an Image Strength of 35% (`0.35`) would result in a `step_schedule_start` of `0.65`.\n", "example": 0.4, "minimum": 0, "maximum": 1, "format": "float", "default": 0.35 }, "InitImageMode": { "type": "string", "description": "Whether to use `image_strength` or `step_schedule_*` to control how much influence the `init_image` has on the result.", "enum": ["IMAGE_STRENGTH", "STEP_SCHEDULE"], "default": "IMAGE_STRENGTH" }, "StepScheduleStart": { "type": "number", "description": "Skips a proportion of the start of the diffusion steps, allowing the init_image to influence the final generated image. Lower values will result in more influence from the init_image, while higher values will result in more influence from the diffusion steps. (e.g. a value of `0` would simply return you the init_image, where a value of `1` would return you a completely different image.)", "default": 0.65, "example": 0.4, "minimum": 0, "maximum": 1 }, "StepScheduleEnd": { "type": "number", "description": "Skips a proportion of the end of the diffusion steps, allowing the init_image to influence the final generated image. Lower values will result in more influence from the init_image, while higher values will result in more influence from the diffusion steps.", "example": 0.01, "minimum": 0, "maximum": 1 }, "MaskImage": { "x-go-type": "[]byte", "type": "string", "description": "Optional grayscale mask that allows for influence over which pixels are eligible for diffusion and at what strength. Must be the same dimensions as the `init_image`. Use the `mask_source` option to specify whether the white or black pixels should be inpainted.", "example": "", "format": "binary" }, "MaskSource": { "type": "string", "description": "For any given pixel, the mask determines the strength of generation on a linear scale. This parameter determines where to source the mask from:\n- `MASK_IMAGE_WHITE` will use the white pixels of the mask_image as the mask, where white pixels are completely replaced and black pixels are unchanged\n- `MASK_IMAGE_BLACK` will use the black pixels of the mask_image as the mask, where black pixels are completely replaced and white pixels are unchanged\n- `INIT_IMAGE_ALPHA` will use the alpha channel of the init_image as the mask, where fully transparent pixels are completely replaced and fully opaque pixels are unchanged" }, "GenerationRequestOptionalParams": { "type": "object", "description": "Represents the optional parameters that can be passed to any generation request.", "properties": { "cfg_scale": { "$ref": "#/components/schemas/CfgScale" }, "clip_guidance_preset": { "$ref": "#/components/schemas/ClipGuidancePreset" }, "sampler": { "$ref": "#/components/schemas/Sampler" }, "samples": { "$ref": "#/components/schemas/Samples" }, "seed": { "$ref": "#/components/schemas/Seed" }, "steps": { "$ref": "#/components/schemas/Steps" }, "style_preset": { "$ref": "#/components/schemas/StylePreset" }, "extras": { "$ref": "#/components/schemas/Extras" } } }, "RealESRGANUpscaleRequestBody": { "type": "object", "properties": { "image": { "$ref": "#/components/schemas/InputImage" }, "width": { "$ref": "#/components/schemas/UpscaleImageWidth" }, "height": { "$ref": "#/components/schemas/UpscaleImageHeight" } }, "required": ["image"] }, "ImageToImageRequestBody": { "type": "object", "properties": { "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "init_image": { "$ref": "#/components/schemas/InitImage" }, "init_image_mode": { "$ref": "#/components/schemas/InitImageMode" }, "image_strength": { "$ref": "#/components/schemas/InitImageStrength" }, "step_schedule_start": { "$ref": "#/components/schemas/StepScheduleStart" }, "step_schedule_end": { "$ref": "#/components/schemas/StepScheduleEnd" }, "cfg_scale": { "$ref": "#/components/schemas/CfgScale" }, "clip_guidance_preset": { "$ref": "#/components/schemas/ClipGuidancePreset" }, "sampler": { "$ref": "#/components/schemas/Sampler" }, "samples": { "$ref": "#/components/schemas/Samples" }, "seed": { "$ref": "#/components/schemas/Seed" }, "steps": { "$ref": "#/components/schemas/Steps" }, "style_preset": { "$ref": "#/components/schemas/StylePreset" }, "extras": { "$ref": "#/components/schemas/Extras" } }, "required": ["text_prompts", "init_image"], "discriminator": { "propertyName": "init_image_mode", "mapping": { "IMAGE_STRENGTH": "#/components/schemas/ImageToImageUsingImageStrengthRequestBody", "STEP_SCHEDULE": "#/components/schemas/ImageToImageUsingStepScheduleRequestBody" } } }, "ImageToImageUsingImageStrengthRequestBody": { "allOf": [ { "type": "object", "properties": { "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "init_image": { "$ref": "#/components/schemas/InitImage" }, "init_image_mode": { "$ref": "#/components/schemas/InitImageMode" }, "image_strength": { "$ref": "#/components/schemas/InitImageStrength" } }, "required": ["text_prompts", "init_image"] }, { "$ref": "#/components/schemas/GenerationRequestOptionalParams" } ] }, "ImageToImageUsingStepScheduleRequestBody": { "allOf": [ { "type": "object", "properties": { "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "init_image": { "$ref": "#/components/schemas/InitImage" }, "init_image_mode": { "$ref": "#/components/schemas/InitImageMode" }, "step_schedule_start": { "$ref": "#/components/schemas/StepScheduleStart" }, "step_schedule_end": { "$ref": "#/components/schemas/StepScheduleEnd" } }, "required": ["text_prompts", "init_image"] }, { "$ref": "#/components/schemas/GenerationRequestOptionalParams" } ] }, "MaskingRequestBody": { "type": "object", "properties": { "init_image": { "$ref": "#/components/schemas/InitImage" }, "mask_source": { "$ref": "#/components/schemas/MaskSource" }, "mask_image": { "$ref": "#/components/schemas/MaskImage" }, "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "cfg_scale": { "$ref": "#/components/schemas/CfgScale" }, "clip_guidance_preset": { "$ref": "#/components/schemas/ClipGuidancePreset" }, "sampler": { "$ref": "#/components/schemas/Sampler" }, "samples": { "$ref": "#/components/schemas/Samples" }, "seed": { "$ref": "#/components/schemas/Seed" }, "steps": { "$ref": "#/components/schemas/Steps" }, "style_preset": { "$ref": "#/components/schemas/StylePreset" }, "extras": { "$ref": "#/components/schemas/Extras" } }, "required": ["text_prompts", "init_image", "mask_source"], "discriminator": { "propertyName": "mask_source", "mapping": { "MASK_IMAGE_BLACK": "#/components/schemas/MaskingUsingMaskImageRequestBody", "MASK_IMAGE_WHITE": "#/components/schemas/MaskingUsingMaskImageRequestBody", "INIT_IMAGE_ALPHA": "#/components/schemas/MaskingUsingInitImageAlphaRequestBody" } } }, "MaskingUsingMaskImageRequestBody": { "allOf": [ { "type": "object", "properties": { "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "init_image": { "$ref": "#/components/schemas/InitImage" }, "mask_source": { "$ref": "#/components/schemas/MaskSource" }, "mask_image": { "$ref": "#/components/schemas/MaskImage" } }, "required": [ "init_image", "mask_image", "text_prompts", "mask_source" ] }, { "$ref": "#/components/schemas/GenerationRequestOptionalParams" } ] }, "MaskingUsingInitImageAlphaRequestBody": { "allOf": [ { "type": "object", "properties": { "text_prompts": { "$ref": "#/components/schemas/TextPrompts" }, "init_image": { "$ref": "#/components/schemas/InitImage" }, "mask_source": { "$ref": "#/components/schemas/MaskSource" } }, "required": ["init_image", "text_prompts", "mask_source"] }, { "$ref": "#/components/schemas/GenerationRequestOptionalParams" } ] }, "TextToImageRequestBody": { "type": "object", "allOf": [ { "type": "object", "properties": { "height": { "$ref": "#/components/schemas/DiffuseImageHeight" }, "width": { "$ref": "#/components/schemas/DiffuseImageWidth" }, "text_prompts": { "$ref": "#/components/schemas/TextPromptsForTextToImage" } }, "required": ["text_prompts"] }, { "$ref": "#/components/schemas/GenerationRequestOptionalParams" } ], "example": { "cfg_scale": 7, "height": 512, "width": 512, "sampler": "K_DPM_2_ANCESTRAL", "samples": 1, "seed": 0, "steps": 30, "text_prompts": [ { "text": "A lighthouse on a cliff", "weight": 1 } ] }, "required": ["text_prompts"] }, "AccountResponseBody": { "type": "object", "properties": { "email": { "type": "string", "description": "The user's email", "example": "example@stability.ai", "format": "email" }, "id": { "type": "string", "description": "The user's ID", "example": "user-1234", "x-go-name": "ID" }, "organizations": { "type": "array", "example": [ { "id": "org-5678", "name": "Another Organization", "role": "MEMBER", "is_default": true }, { "id": "org-1234", "name": "My Organization", "role": "MEMBER", "is_default": false } ], "items": { "$ref": "#/components/schemas/OrganizationMembership" }, "description": "The user's organizations" }, "profile_picture": { "type": "string", "description": "The user's profile picture", "example": "https://api.stability.ai/example.png", "format": "uri" } }, "required": ["id", "email", "organizations"] }, "BalanceResponseBody": { "type": "object", "properties": { "credits": { "type": "number", "description": "The balance of the account/organization associated with the API key", "example": 0.41122252265928866, "format": "double" } }, "example": { "credits": 0.07903292496944721 }, "required": ["credits"] }, "ListEnginesResponseBody": { "type": "array", "description": "The engines available to your user/organization", "items": { "$ref": "#/components/schemas/Engine" }, "example": [ { "description": "Stability-AI Stable Diffusion v1.6", "id": "stable-diffusion-v1-6", "name": "Stable Diffusion v1.6", "type": "PICTURE" }, { "description": "Stability-AI Stable Diffusion XL v1.0", "id": "stable-diffusion-xl-1024-v1-0", "name": "Stable Diffusion XL v1.0", "type": "PICTURE" } ] }, "FinishReason": { "type": "string", "description": "The result of the generation process.\n- `SUCCESS` indicates success\n- `ERROR` indicates an error\n- `CONTENT_FILTERED` indicates the result affected by the content filter and may be blurred.\n\nThis header is only present when the `Accept` is set to `image/png`. Otherwise it is returned in the response body.", "enum": ["SUCCESS", "ERROR", "CONTENT_FILTERED"] }, "Image": { "type": "object", "properties": { "base64": { "type": "string", "x-go-type-skip-optional-pointer": true, "description": "Image encoded in base64" }, "finishReason": { "type": "string", "x-go-type-skip-optional-pointer": true, "example": "CONTENT_FILTERED", "enum": ["SUCCESS", "ERROR", "CONTENT_FILTERED"] }, "seed": { "type": "number", "x-go-type-skip-optional-pointer": true, "description": "The seed associated with this image", "example": 1229191277 } }, "example": [ { "base64": "...very long string...", "finishReason": "SUCCESS", "seed": 1050625087 }, { "base64": "...very long string...", "finishReason": "CONTENT_FILTERED", "seed": 1229191277 } ] }, "OrganizationMembership": { "type": "object", "properties": { "id": { "type": "string", "example": "org-123456", "x-go-name": "ID" }, "is_default": { "type": "boolean", "example": false }, "name": { "type": "string", "example": "My Organization" }, "role": { "type": "string", "example": "MEMBER" } }, "required": ["id", "name", "role", "is_default"] } }, "parameters": { "upscaleEngineID": { "in": "path", "name": "engine_id", "required": true, "schema": { "type": "string" }, "examples": { "ESRGAN_X2_PLUS": { "description": "ESRGAN x2 Upscaler", "value": "esrgan-v1-x2plus" } } }, "engineID": { "examples": { "default": { "value": "stable-diffusion-v1-6", "description": "Stable Diffusion v1.6" }, "stable-diffusion-xl-1024-v1-0": { "value": "stable-diffusion-xl-1024-v1-0", "description": "Stable Diffusion XL v1.0" } }, "in": "path", "name": "engine_id", "required": true, "schema": { "type": "string" } }, "organization": { "allowEmptyValue": false, "description": "Allows for requests to be scoped to an organization other than the user's default. If not provided, the user's default organization will be used.", "example": "org-123456", "in": "header", "name": "Organization", "x-go-name": "OrganizationID", "schema": { "type": "string" } }, "stabilityClientID": { "allowEmptyValue": false, "description": "Used to identify the source of requests, such as the client application or sub-organization. Optional, but recommended for organizational clarity.", "example": "my-great-plugin", "in": "header", "name": "Stability-Client-ID", "schema": { "type": "string" } }, "stabilityClientVersion": { "allowEmptyValue": false, "description": "Used to identify the version of the application or service making the requests. Optional, but recommended for organizational clarity.", "example": "1.2.1", "in": "header", "name": "Stability-Client-Version", "schema": { "type": "string" } }, "accept": { "allowEmptyValue": false, "in": "header", "name": "Accept", "description": "The format of the response. Leave blank for JSON, or set to 'image/png' for a PNG image.", "schema": { "default": "application/json", "enum": ["application/json", "image/png"], "type": "string" } } }, "securitySchemes": { "STABILITY_API_KEY": { "type": "apiKey", "scheme": "bearer", "name": "authorization", "in": "header", "description": "Use your [Stability API key](https://platform.stability.ai/account/keys) to authentication requests to this App." } }, "responses": { "401": { "description": "unauthorized: API key missing or invalid", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "9160aa70-222f-4a36-9eb7-475e2668362a", "name": "unauthorized", "message": "missing authorization header" } } } }, "403": { "description": "permission_denied: You lack the necessary permissions to perform this action", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "5cf19777-d17f-49fe-9bd9-39ff0ec6bb50", "name": "permission_denied", "message": "You do not have permission to access this resource" } } } }, "404": { "description": "not_found: The requested resource/engine was not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "92b19e7f-22a2-4e71-a821-90edda229293", "name": "not_found", "message": "The specified engine (ID some-fake-engine) was not found." } } } }, "500": { "description": "server_error: Some unexpected server error occurred", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "f81964d6-619b-453e-97bc-9fd7ac3f04e7", "name": "server_error", "message": "An unexpected server error occurred, please try again." } } } }, "GenerationResponse": { "description": "Generation successful.", "content": { "application/json": { "schema": { "description": "An array of results from the generation request, where each image is a base64 encoded PNG.", "type": "object", "properties": { "artifacts": { "type": "array", "x-go-type-skip-optional-pointer": true, "items": { "$ref": "#/components/schemas/Image" } } } } }, "image/png": { "example": "The bytes of the generated image, what did you expect?", "schema": { "description": "The bytes of the generated PNG image", "format": "binary", "type": "string" } } }, "headers": { "Content-Length": { "$ref": "#/components/headers/Content-Length" }, "Content-Type": { "$ref": "#/components/headers/Content-Type" }, "Finish-Reason": { "$ref": "#/components/headers/Finish-Reason" }, "Seed": { "$ref": "#/components/headers/Seed" } } }, "400FromGeneration": { "description": "bad_request: one or more parameters were invalid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "296a972f-666a-44a1-a3df-c9c28a1f56c0", "name": "bad_request", "message": "init_image: is required" } } } }, "400FromUpscale": { "description": "One or more parameters were invalid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "id": "296a972f-666a-44a1-a3df-c9c28a1f56c0", "name": "bad_request", "message": "image: is required" } } } } }, "headers": { "Content-Length": { "required": true, "schema": { "type": "integer" } }, "Content-Type": { "required": true, "schema": { "enum": ["application/json", "image/png"], "type": "string" } }, "Finish-Reason": { "schema": { "$ref": "#/components/schemas/FinishReason" } }, "Seed": { "example": 3817857576, "schema": { "example": 787078103, "type": "integer" }, "description": "The seed used to generate the image. This header is only present when the `Accept` is set to `image/png`. Otherwise it is returned in the response body." } } }, "x-tagGroups": [ { "name": "Stable Image", "tags": ["Generate", "Upscale", "Edit", "Control", "Results"] }, { "name": "3D & Video", "tags": ["3D", "Image-to-Video"] }, { "name": "Version 1", "tags": ["SDXL 1.0 & SD1.6", "Engines", "User"] } ] } ================================================ FILE: packages/backend/server.py ================================================ from app.log_config import root_logger import sys import os from dotenv import load_dotenv load_dotenv() from app.flask.socketio_init import flask_app, socketio import app.flask.sockets import app.flask.routes import app.tasks.single_thread_tasks.browser.async_browser_task if __name__ == "__main__": host = os.getenv("HOST", "127.0.0.1") port = int(os.getenv("PORT", 8000)) root_logger.info(f"Starting application on {host}:{port}...") root_logger.info("You can stop the application by pressing Ctrl+C at any time.") # If we're running in a PyInstaller bundle if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): os.environ["PLAYWRIGHT_BROWSERS_PATH"] = os.path.join( sys._MEIPASS, "ms-playwright" ) root_logger.warning("Protocol set to HTTP") socketio.run(flask_app, port=port, host=host) ================================================ FILE: packages/backend/tests/unit/test_processor_factory.py ================================================ import unittest from app.processors.factory.processor_factory_iter_modules import ( ProcessorFactoryIterModules, ) from app.processors.components.processor import BasicProcessor, ContextAwareProcessor class DummyProcessor(BasicProcessor): processor_type = "dummy_processor" def process(self): pass def cancel(self): pass class APIDummyProcessor(ContextAwareProcessor): processor_type = "api_dummy_processor" def __init__(self, config, context=None): super().__init__(config) self._processor_context = context def process(self): pass def cancel(self): pass class TestProcessorFactory(unittest.TestCase): def setUp(self): self.factory = ProcessorFactoryIterModules() def test_register_and_create_simple_processor(self): self.factory.register_processor(DummyProcessor.processor_type, DummyProcessor) processor = self.factory.create_processor( {"processorType": "dummy_processor", "name": "dummy_processor"} ) self.assertIsInstance(processor, DummyProcessor) self.assertIsInstance(processor, BasicProcessor) def test_create_unknown_processor_raises_exception(self): with self.assertRaises(ValueError): self.factory.create_processor( {"processorType": "unknown_processor", "name": "unknown_processor"} ) def test_create_processor_with_api_context_data(self): self.factory.register_processor( APIDummyProcessor.processor_type, APIDummyProcessor ) processor = self.factory.create_processor( {"processorType": "api_dummy_processor", "name": "api_dummy_processor"}, context_data="api_data", ) self.assertIsInstance(processor, APIDummyProcessor) self.assertIsInstance(processor, ContextAwareProcessor) self.assertEqual(processor._processor_context, "api_data") ================================================ FILE: packages/backend/tests/unit/test_processor_launcher.py ================================================ import unittest from unittest.mock import MagicMock, Mock, patch, mock_open from app.processors.launcher.basic_processor_launcher import BasicProcessorLauncher from app.processors.factory.processor_factory_iter_modules import ( ProcessorFactoryIterModules, ) from dotenv import load_dotenv from app.tasks.single_thread_tasks.browser.browser_task import ( stop_browser_thread, ) load_dotenv() from app.flask.socketio_init import socketio class NoInputMock(MagicMock): def __getattr__(self, name): if name == "inputs": raise AttributeError( f"'{type(self).__name__}' object has no attribute 'inputs'" ) return super().__getattr__(name) class TestProcessorLauncher(unittest.TestCase): def test_load_config_data_valid_file(self): factory = ProcessorFactoryIterModules() launcher = BasicProcessorLauncher(factory, None) m = mock_open(read_data='{"key": "value"}') with patch("builtins.open", m): data = launcher._load_config_data("fake_file_path") self.assertEqual(data, {"key": "value"}) def test_link_processors_valid(self): factory = ProcessorFactoryIterModules() launcher = BasicProcessorLauncher(factory, None) processor1 = Mock() processor1.name = "processor1" processor1.inputs = [{"inputNode": "processor2"}] processor2 = NoInputMock() processor2.name = "processor2" processors = { "processor1": processor1, "processor2": processor2, } launcher._link_processors(processors) processor1.add_input_processor.assert_called_once_with(processor2) stop_browser_thread() ================================================ FILE: packages/backend/tests/unit/test_stable_diffusion_stabilityai_prompt_processor.py ================================================ import unittest from unittest.mock import ANY, patch, Mock import re from app.processors.components.core.stable_diffusion_stabilityai_prompt_processor import ( StableDiffusionStabilityAIPromptProcessor, ) from app.storage.local_storage_strategy import LocalStorageStrategy from app.processors.components.core.input_processor import InputProcessor from tests.utils.processor_context_mock import ProcessorContextMock class TestStableDiffusionStabilityAIPromptProcessor(unittest.TestCase): @staticmethod def get_default_valid_config(): return { "inputs": [{"inputNode": "0rhbnaol3#gpt-no-context-prompt"}], "name": "vd6up8r0m#stable-diffusion-stabilityai-prompt", "processorType": "stable-diffusion-stabilityai-prompt", "size": "1024x1024", "x": -1961.3132869508825, "y": -73.26855714327525, } @patch("requests.post") def test_process_returns_valid_image_url_on_successful_api_response( self, mock_post ): # Arrange API_KEY = "FAKE" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "artifacts": [{"base64": "R0lGODlhAQABAAAAACw="}] } mock_post.return_value = mock_response url_pattern = re.compile(r"http?://[^\s]+") config = self.get_default_valid_config() clean_name = config["name"].replace("#", "") api_context_data = ProcessorContextMock(API_KEY) processor = StableDiffusionStabilityAIPromptProcessor(config, api_context_data) processor.set_storage_strategy(LocalStorageStrategy()) # Act url = processor.process() # Assert self.assertTrue(url_pattern.match(url)) self.assertIn(clean_name, url) self.assertTrue(url.endswith(".png")) mock_post.assert_called_once_with( ANY, headers=ANY, json=ANY, ) @patch("requests.post") def test_process_transmit_prompt_to_api(self, mock_post): # Arrange API_KEY = "FAKE" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "artifacts": [{"base64": "R0lGODlhAQABAAAAACw="}] } mock_post.return_value = mock_response config = self.get_default_valid_config() expected_prompt = "Expected prompt" config["prompt"] = expected_prompt api_context_data = ProcessorContextMock(API_KEY) processor = StableDiffusionStabilityAIPromptProcessor(config, api_context_data) processor.set_storage_strategy(LocalStorageStrategy()) # Act processor.process() # Assert called_args = mock_post.call_args[1] sent_json = called_args.get("json") transmitted_prompts = sent_json["text_prompts"] self.assertTrue( any(prompt["text"] == expected_prompt for prompt in transmitted_prompts) ) @patch("requests.post") def test_when_linked_to_input_node_transmits_input_node_output_to_the_api( self, mock_post ): # Arrange API_KEY = "FAKE" expected_prompt = "Expected prompt" input_processor = InputProcessor( { "inputs": [], "name": "p00tklq5w#input-text", "processorType": "input-text", "inputText": expected_prompt, } ) input_processor.set_output(expected_prompt) mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "artifacts": [{"base64": "R0lGODlhAQABAAAAACw="}] } mock_post.return_value = mock_response config = self.get_default_valid_config() api_context_data = ProcessorContextMock(API_KEY) processor = StableDiffusionStabilityAIPromptProcessor(config, api_context_data) processor.set_storage_strategy(LocalStorageStrategy()) processor.add_input_processor(input_processor) # Act processor.process() # Assert called_args = mock_post.call_args[1] sent_json = called_args.get("json") transmitted_prompts = sent_json["text_prompts"] self.assertTrue( any(prompt["text"] == expected_prompt for prompt in transmitted_prompts) ) ================================================ FILE: packages/backend/tests/utils/openai_mock_utils.py ================================================ from unittest.mock import Mock def create_mocked_openai_response( model="gpt-4", api_key="000000000", response_content="Mocked Response" ): """ Create a mocked response for OpenAI. :param model: The model to be used. :param api_key: The API key to be used. :param response_content: The content for the mocked response. :return: A mocked response for OpenAI. """ mock_message = Mock() mock_message.content = response_content mock_choice = Mock() mock_choice.message = mock_message mock_response = Mock() mock_response.choices = [mock_choice] return mock_response ================================================ FILE: packages/backend/tests/utils/processor_context_mock.py ================================================ from typing import List from app.processors.context.processor_context import ProcessorContext from typing import List class ProcessorContextMock(ProcessorContext): def __init__(self, api_key, user_id=0, session_id=0) -> None: super().__init__() self.api_key = api_key self.user_id = user_id self.session_id = session_id def get_context(self): return self.api_key def get_current_user_id(self): return self.user_id def get_session_id(self): return self.user_id def get_parameter_names(self) -> List[str]: return super().get_parameter_names() def get_value(self, name): if "api_key" in name: return self.api_key return super().get_value(name) def is_using_personal_keys(self, source_name): return False ================================================ FILE: packages/backend/tests/utils/processor_factory_mock.py ================================================ import logging import random import eventlet import time from injector import singleton from unittest.mock import MagicMock from app.processors.factory.processor_factory_iter_modules import ( ProcessorFactoryIterModules, ) from app.processors.components.core.processor_type_name_utils import ( ProcessorType, ) from .processor_context_mock import ProcessorContextMock @singleton class ProcessorFactoryMock(ProcessorFactoryIterModules): MIN_DELAY = 0.1 MAX_DELAY = 1 NON_MOCKED_PROCESSORS = [ ProcessorType.INPUT_TEXT.value, ProcessorType.INPUT_IMAGE.value, ProcessorType.URL_INPUT.value, ProcessorType.DISPLAY.value, ProcessorType.TRANSITION.value, ] def __init__( self, fake_text_output=None, fake_img_output=None, fake_multiple_output=None, with_delay=False, sleep_duration=None, ): super().__init__() self._mock_processors = {} self.fake_text_output = fake_text_output self.fake_img_output = fake_img_output self.fake_multiple_output = fake_multiple_output self.with_delay = with_delay def create_mock_processor( self, config, processor_type: ProcessorType, processor_class: str ): mock_processor = MagicMock(spec=processor_class) mock_processor.name = config.get("name", "default_processor_name") mock_processor.processor_type = processor_type mock_processor.input_processors = [] mock_processor._processor_context = ProcessorContextMock("") if config.get("inputs") is not None and config.get("inputs") != []: mock_processor.inputs = config.get("inputs") def fake_process(*args, **kwargs): if self.with_delay: sleep_duration = random.uniform( ProcessorFactoryMock.MIN_DELAY, ProcessorFactoryMock.MAX_DELAY ) eventlet.sleep(sleep_duration) if config.get("sleepDuration") is not None: sleep_duration = config.get("sleepDuration") logging.info(f"Sleeping for {sleep_duration} seconds") eventlet.sleep(sleep_duration) logging.info("Awake") if mock_processor.processor_type in [ ProcessorType.DALLE_PROMPT.value, ProcessorType.STABLE_DIFFUSION_STABILITYAI_PROMPT.value, ]: output = ( [self.fake_img_output] if self.fake_img_output is not None else [ "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/v0.4.0-sample-1.png" ] ) elif mock_processor.processor_type in [ ProcessorType.AI_DATA_SPLITTER.value ]: output = ( self.fake_multiple_output if self.fake_multiple_output is not None else ["Lorem Ipsum", "Lorem Ipsum"] ) else: output = ( self.fake_text_output if self.fake_text_output is not None else "Lorem Ipsum" ) mock_processor.set_output(output) mock_processor.is_finished = True return output def fake_process_raise_error(*args, **kwargs): logging.error("MockProcessor - Fake Error") raise Exception("Mock Processor error") def fake_add_input_processor(input_processor): mock_processor.input_processors.append(input_processor) def get_input_processors(): return mock_processor.input_processors def fake_has_dynamic_behavior(): return False def fake_get_input_by_name(input_name, default_value=""): return default_value mock_processor.process_and_update = ( fake_process if config.get("raiseError", False) == False else fake_process_raise_error ) mock_processor.add_input_processor = fake_add_input_processor mock_processor.get_input_processors = get_input_processors mock_processor.has_dynamic_behavior = fake_has_dynamic_behavior mock_processor.get_input_by_name = fake_get_input_by_name self._mock_processors[processor_type] = mock_processor return mock_processor def create_processor(self, config, context=None, storage_strategy=None): processor_type = config["processorType"] processor_class = self._processors.get(processor_type) if not processor_class: raise ValueError(f"Processor type '{processor_type}' not supported") if ( processor_type in ProcessorFactoryMock.NON_MOCKED_PROCESSORS and config.get("raiseError", False) == False ): processor = processor_class(config=config) else: processor = self.create_mock_processor( config, processor_type, processor_class ) return processor ================================================ FILE: packages/ui/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* #amplify-do-not-edit-begin amplify/\#current-cloud-backend amplify/.config/local-* amplify/logs amplify/mock-data amplify/mock-api-resources amplify/backend/amplify-meta.json amplify/backend/.temp build/ dist/ node_modules/ aws-exports.js awsconfiguration.json amplifyconfiguration.json amplifyconfiguration.dart amplify-build-config.json amplify-gradle-config.json amplifytools.xcconfig .secret-* **.sample #amplify-do-not-edit-end ================================================ FILE: packages/ui/.prettierignore ================================================ node_modules # Ignore artifacts: build coverage ================================================ FILE: packages/ui/Dockerfile ================================================ FROM node:21 as build WORKDIR /app ARG VITE_APP_WS_HOST ARG VITE_APP_WS_PORT ARG VITE_APP_API_REST_PORT ARG VITE_APP_USE_HTTPS ARG VITE_APP_VERSION ENV VITE_APP_WS_HOST=$VITE_APP_WS_HOST ENV VITE_APP_WS_PORT=$VITE_APP_WS_PORT ENV VITE_APP_API_REST_PORT=$VITE_APP_API_REST_PORT ENV VITE_APP_USE_HTTPS=$VITE_APP_USE_HTTPS ENV VITE_APP_VERSION=$VITE_APP_VERSION COPY package.json package-lock.json /app/ RUN npm ci COPY . /app/ RUN npm run build RUN ls -al /app FROM nginx:1.21 COPY --from=build ./app/build /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ================================================ FILE: packages/ui/README.md ================================================ # Getting Started with Create React App This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). ## Available Scripts In the project directory, you can run: ### `npm start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. ### `npm test` Launches the test runner in the interactive watch mode.\ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. ### `npm run build` Builds the app for production to the `build` folder.\ It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. ### `npm run eject` **Note: this is a one-way operation. Once you `eject`, you can’t go back!** If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. ## Learn More You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). To learn React, check out the [React documentation](https://reactjs.org/). ================================================ FILE: packages/ui/index.html ================================================ AI Flow
================================================ FILE: packages/ui/jest.config.ts ================================================ import type { Config } from "@jest/types"; const config: Config.InitialOptions = { verbose: true, preset: "ts-jest", testEnvironment: "node", testMatch: ["**/test/**/*.ts", "**/?(*.)+(spec|test).ts"], }; export default config; ================================================ FILE: packages/ui/nginx.conf ================================================ server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; add_header Content-Security-Policy "script-src 'self' 'unsafe-inline';"; } } ================================================ FILE: packages/ui/package.json ================================================ { "name": "ai-flow-front", "version": "0.11.3", "private": true, "dependencies": { "@headlessui/react": "^1.7.17", "@mantine/core": "^7.7.1", "@mantine/hooks": "^7.7.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.3.12", "@types/lodash.debounce": "^4.0.8", "@types/node": "^20.12.12", "@types/three": "^0.164.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.14", "axios": "^1.6.2", "dotenv": "^16.0.3", "framer-motion": "^10.16.4", "github-markdown-css": "^5.4.0", "i18next": "^22.5.0", "i18next-browser-languagedetector": "^7.0.1", "i18next-http-backend": "^2.2.1", "lodash.debounce": "^4.0.8", "polished": "^4.2.2", "rdndmb-html5-to-touch": "^8.0.3", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dnd-multi-backend": "^8.0.3", "react-dnd-touch-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-grid-layout": "^1.4.4", "react-i18next": "^12.3.1", "react-icons": "^4.11.0", "react-joyride": "^2.7.2", "react-markdown": "^9.0.0", "react-resizable": "^3.0.5", "react-sketch-canvas": "^6.2.0", "react-switch": "^7.0.0", "react-syntax-highlighter": "^15.6.1", "react-toastify": "^9.1.3", "react-tooltip": "^5.13.1", "react18-json-view": "^0.2.9", "reactflow": "^11.7.2", "remark-gfm": "^4.0.0", "socket.io-client": "^4.6.1", "styled-components": "^5.3.10", "tailwindcss": "^3.3.2", "three": "^0.164.1", "typescript": "^4.9.5", "typescript-json-schema": "^0.63.0", "video.js": "^8.12.0", "videojs-wavesurfer": "^3.10.0", "vite": "^5.2.11", "vite-plugin-svgr": "^4.2.0", "web-vitals": "^2.1.4" }, "scripts": { "start": "vite --host", "build": "tsc && vite build", "serve": "vite preview", "test": "vitest", "test:e2e": "playwright test" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@playwright/test": "^1.45.1", "@types/dompurify": "^3.0.2", "@types/jest": "^29.5.12", "@types/react": "^18.3.3", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^18.3.0", "@types/react-grid-layout": "^1.3.5", "@types/react-syntax-highlighter": "^15.5.13", "@types/styled-components": "^5.1.26", "@types/video.js": "^7.3.58", "@vitest/coverage-v8": "^1.6.0", "jsdom": "^24.0.0", "postcss": "^8.4.38", "postcss-preset-mantine": "^1.13.0", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.4", "prettier-plugin-tailwindcss": "^0.5.11", "ts-jest": "^29.1.2", "vite-bundle-visualizer": "^1.2.1", "vitest": "^1.6.0" } } ================================================ FILE: packages/ui/postcss.config.cjs ================================================ module.exports = { plugins: { "postcss-preset-mantine": {}, "postcss-simple-vars": { variables: { "mantine-breakpoint-xs": "36em", "mantine-breakpoint-sm": "48em", "mantine-breakpoint-md": "62em", "mantine-breakpoint-lg": "75em", "mantine-breakpoint-xl": "88em", }, }, }, }; ================================================ FILE: packages/ui/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: packages/ui/prettier.config.js ================================================ module.exports = { plugins: ['prettier-plugin-tailwindcss'], } ================================================ FILE: packages/ui/public/health ================================================ OK ================================================ FILE: packages/ui/public/locales/en/aiActions.json ================================================ { "Summary": "Summary", "SpellCheck": "Spell Check", "VisualPrompt": "Visual Prompt", "ConstructiveCritique": "Constructive Critique", "SimpleExplanation": "Simple Explanation", "Paraphrase": "Paraphrase", "SentimentAnalysis": "Sentiment Analysis", "TextExtension": "Text Extension", "ClickToShowOutput": "Click to show output" } ================================================ FILE: packages/ui/public/locales/en/config.json ================================================ { "configurationTitle": "Configuration", "apiKeyDisclaimer": "We do not use or store your API keys.", "openSourceDisclaimer": "The application code is open source.", "apiKeyRevokeReminder": "Remember, you can revoke your keys at any time and generate new ones.", "closeButtonLabel": "Close", "validateButtonLabel": "Validate", "likeProjectPrompt": "If you like this project, you can add a star on:", "supportProjectPrompt": "You can support the future of the project and contact us through", "Logout": "Logout", "sections.core": "Base parameters", "parameters.core.openai_api_key": "OpenAI API Key", "parameters.core.stabilityai_api_key": "StabilityAI API Key", "parameters.core.replicate_api_key": "Replicate API Key", "parameters.extension.anthropic_api_key": "Anthropic API Key", "sections.extension": "Extensions", "userTabLabel": "User parameters", "appParametersLabel": "App parameters", "displayTabLabel": "Display parameters", "nodesDisplayed": "Nodes enabled", "configUpdated": "Configuration updated successfully", "ShowMinimap": "Show minimap", "UI": "User Interface", "input": "Inputs", "models": "Models", "tools": "Tools", "parameters.extension.deepseek_api_key": "DeepSeek API Key", "parameters.extension.openrouter_api_key": "OpenRouter API Key", "incompleteLoadingPleaseRestart": "The application failed to load all data. Please restart the app." } ================================================ FILE: packages/ui/public/locales/en/dialogs.json ================================================ { "attachNodeTitle": "Attach Node", "attachNodeAction": "Attach" } ================================================ FILE: packages/ui/public/locales/en/flow.json ================================================ { "Flow": "Flow", "AddTab": "Add Tab", "ShowOnlyOutputs": "Show only outputs", "ShowOnlyParams": "Show only params", "ApiKeyRequiredMessage": "Please provide your API Key in the configuration settings to run your flow successfully.", "Prompt": "Prompt", "ClickToShowOutput": "Click to show output", "RoleInitPrompt": "Indicate the role you wish the AI to adopt in the upcoming interactions. For example, 'Behave as a Literary Critic'.", "EnterURL": "Web Extractor", "YoutubeTranscriptNodeName": "Youtube Transcript", "URLPlaceholder": "Input a URL", "Input": "Inputs", "InputImage": "Image", "InputPlaceholder": "Enter your text here to serve as input for other nodes.", "InputImagePlaceholder": "Enter the URL of the image you want to use.", "DALLE": "DALL-E", "ClickToShowImageOutput": "Click to show image output", "JsonView": "JSON View", "TopologicalView": "Outputs", "currentNodeView": "Current Node", "Upload": "Upload", "Download": "Download", "Text": "Text", "URL": "URL", "YoutubeVideo": "Youtube Transcript", "Models": "Models", "GPT": "GPT Model", "GPTPrompt": "GPT Prompt", "DataSplitter": "Data Splitter", "ReplicateModel": "Replicate", "NoContextPrompt": "GPT Prompt", "PromptPlaceholder": "Enter your prompt here, for example 'Create a Twitter thread based on the data I sent you.'", "MergePromptPlaceholder": "Enter your text for the merge, using ${input-1} and ${input-2} as placeholders. You can add additional text, for example: \n Answer to ${input-1} by considering ${input-2}.", "VisionPromptPlaceholder": "Please enter your prompt here to start the image analysis. For example, you could write 'Describe this image'", "VisionImageURLPlaceholder": "Enter the URL of the image you want to use.", "DallEPromptPlaceholder": "Enter your prompt here, for example 'A dog and a cat playing in the desert'", "ImageGeneration": "Image Generation", "AdvancedSection": "Advanced", "AiAction": "AI Action", "LLMPrompt": "GPT", "AiDataSplitter": "Data Splitter", "MergerNode": "Merge Text", "inputHelp": "This node is used to input text.", "inputImageHelp": "This node is used to input an image URL.", "urlInputHelp": "Enter a valid URL and the node will retrieve the data from this URL.", "youtubeTranscriptHelp": "This node retrieves the subtitles of a YouTube video from its URL.", "gptHelp": "This node allows you to configure a GPT model, specify its role, and the data it will use to respond.", "gptPromptHelp": "This node allows you to query a GPT model. It shares its context with other nodes connected to the model.", "noContextPromptHelp": "This node allows you to query GPT without context, just from input data and a prompt. You don't have to connect it to a GPT Model.", "dallePromptHelp": "This node uses the DALL-E model to generate images from a textual description.", "stableDiffusionPromptHelp": "This node uses the Stable Diffusion model to generate images from a textual description.", "stableVideoDiffusionPromptHelp": "This node uses Replicate to launch Stable Video Diffusion.", "aiActionPromptHelp": "This node uses GPT-4 to perform simple action, without having to write a prompt.", "llmPromtHelp": "This nodes allows you to send prompt to GPT-3.5 or GPT-4", "replicateHelp": "This nodes uses Replicate to give access to a large amount of models.", "mergerPromptHelp": "This nodes allows you to merge 2 inputs.", "gptVisionPromptHelp": "This node uses GPT-4 Vision and takes an image URL as input.", "dataSplitterHelp": "This node is used to split data into several parts. You can specify upstream how many parts you want to create. You can also run it individually so that it finds the exact number of outputs to generate.", "socketConnectionLost": "The connection has been lost.", "ClickToSelectModel": "Click to select model", "Or": "OR", "EnterModelNameDirectly": "Enter model name directly", "Load": "Load", "LoadMore": "Load more", "SpotlightModels": "Spotlight Models", "AllModels": "All Models", "EdgeType": "Edge type", "CannotChangeTabWhileRunning": "Cannot change tab while running", "EnterUrlToDesiredFile": "Enter file URL", "Transition": "Transition", "transitionHelp": "This node can be used to organize the flow.", "MissingFieldsMessage": "Required fields are missing", "Node": "Node", "MissingFields": "Missing fields", "CannotDeleteLastFlow": "Cannot delete the last flow", "HideSidebar": "Hide sidebar", "ShowSidebar": "Show sidebar", "fileUploadHelp": "This node can be used to load a file.", "llmPromptHelp": "This node allows you to send prompt to GPT-3.5 or GPT-4", "Output": "Output", "Inputs": "Inputs", "Parameters": "Parameters", "Duplicate": "Duplicate", "OpeninSidepane": "Open in sidepane", "ClearOutput": "Clear Output", "RemoveNode": "Remove Node", "ExpiredURL": "Expired URL", "NoNodeSelected": "No node selected yet.", "ClickOnNodeToSelectIt": "Click on any node to select it.", "Field": "Field", "DragAndDropNodes": "Drag and drop nodes onto the canvas to add them.", "CopiedToClipboard": "Copied to clipboard.", "DocumentToText": "Document-to-Text", "documentToTextHelp": "Convert .pdf .txt .csv .json .html file to simple text", "TextToSpeech": "Text-to-Speech", "textToSpeechHelp": "Convert a text to an audio file using OpenAI tts model", "error.upload_failed": "Upload failed. Please check your configuration to enable file upload.", "InputTextPlaceholder": "Enter your text here", "DownloadFile": "Download File", "FileUploaded": "File uploaded", "GenericPromptPlaceholder": "Enter your prompt here", "GenericNegativePromptPlaceholder": "Enter your negative prompt here", "EnterCustomName": "Enter custom name", "NodeColor": "Change node color", "ChangeName": "Change name", "RemoveFlow": "Remove flow", "HideHint": "Hide", "TextDocumentHint": "Please note that this node only provides files as URLs. To use a document (.pdf, .txt) in text format, consider using the 'Document-to-Text' node.", "Display": "Display", "displayHelp": "This resizable node allows you to display content.", "Validate": "Validate", "AI": "AI", "Separator": "Separator", "ClaudeAnthropic": "Claude", "claudeAnthropichHelp": "This node uses Claude from Anthropic to generate text.", "noDataAvailableForThisNode": "No data available for this node", "learnMore": "Learn more:", "Help": "Help", "cookiesConsentLabelPlaceholder": "Agree to all", "cookiesConsentLabelHelp": "For some pages, we need to click on the cookie consent button to access the data. This instruction helps locate the button.", "EditTextContent": "Edit text content", "ShowCoordinates": "Show Coordinates", "ShowNodesConfig": "Show Nodes Config", "DeleteAll": "Delete All", "DeleteOutputs": "Delete Outputs", "ReplaceText": "Replace Text", "ReplaceTextInputPlaceholder": "Enter the full text where the term will be replaced.", "ReplaceTextSearchPlaceholder": "Enter the term or regex pattern to be replaced.", "ReplaceTextReplacePlaceholder": "Enter the replacement term.", "replaceTextNodeHelp": "Use this node to find and replace specific text or patterns within the input.", "openaio1Help": "Advanced language models trained for complex reasoning, excelling in scientific, mathematical, and programming challenges.", "ContextPlaceholder": "Additionnal context that will be used to answer your prompt.", "deepSeekHelp": "Access DeepSeek LLMs through this node.", "openRouterHelp": "OpenRouter provides completion API to multiple models & providers. This nodes requires you to provide an API Key.", "Generate Number": "Generate Number", "generateNumberHelp": "Generate a random number", "httpGetProcessorURLPlaceholder": "Enter the URL to request", "httpGetProcessorURLDescription": "The URL that the HTTP GET request will be sent to.", "httpGetProcessorHeadersPlaceholder": "Enter headers in JSON format", "httpGetProcessorHeadersDescription": "The headers to include in the HTTP GET request.", "httpGetProcessorHelp": "Send an HTTP GET request with the specified headers.", "gptImageHelp": "Generate or Edit an image using GPT Image", "gptImageMaskDescription": "You can provide a mask to indicate where the image should be edited. You can use the prompt to describe the full new image, not just the erased area. If you provide multiple input images, the mask will be applied to the first image.", "dallEDeprecated": "Most recent OpenAI models are now available via the new GPT Image node and are superior to DALL-E. DALL-E remains available if needed.", "TTSInstructionPlaceholder": "Ex : Speak in a cheerful and positive tone.", "TTSInstructionDescription": " Prompt the model to control aspects of speech (accent, emotional range, intonation, speed, tone, ...)", "PopularModels": "Popular Models", "removeBackgroundDescription": "Remove the background from an image using the StabilityAI API.", "upscaleFastDescription": "Upscale an image using StabilityAI API", "fluxDescription": "Generate an image using the FLUX model.", "fluxKontextDescription": "A state-of-the-art text-based image editing model that delivers high-quality outputs with excellent prompt following and consistent results for transforming images through natural language", "faceswapDescription": "Seamlessly swap faces between images, allowing for realistic and precise facial replacements.", "removeBgDescription": "Remove the background from an image using lucataco/remove-bg from Replicate API.", "upscaleDescription": "Real-ESRGAN with optional face correction and adjustable upscale", "GoogleUpscaleDescription": "Upscale an image using Google's model.", "moondreamDescription": "Moondream is a vision model that responds to prompts about a given image.", "llamaDescription": "Meta's flagship 405 billion parameter language model, fine-tuned for chat completions.", "imagenDescription": "Imagen produce stunning, detailed images with precision.", "recraftSVGDescription": "Recraft SVG offers advanced vector image generation, enabling scalable and creative SVG designs.", "recraftDescription": "Recraft V3 is a text-to-image model with the ability to generate long texts, and images in a wide list of styles. ", "video01Description": "Video-01 provides dynamic video generation.", "video01LiveDescription": "Video-01-Live provides dynamic video generation, ideal for 2D animation.", "klingDescription": "Kling delivers robust video generation and animation solutions, available in both professional and standard versions.", "veo3Description": "Google’s flagship Veo 3 text to video model, with audio" } ================================================ FILE: packages/ui/public/locales/en/nodeHelp.json ================================================ { "input-text": { "description": "Text Node can be used to transfer text input to other nodes.", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/Input-nodes", "label": "Add an input in AI-FLOW" } ] }, "url_input": { "description": "Retrieve textual content from an URL.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/web-extractor-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/Input-nodes#url", "label": "Add an input in AI-FLOW" } ] }, "llm-prompt": { "description": "Processes inputs using GPT models by OpenAI, which can understand and generate responses based on the context provided by the user.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/gpt-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/summarize-doc-post", "label": "How to Summarize Documents or Ask Questions Using AI-FLOW" }, { "url": "https://docs.ai-flow.net/blog/summarize-ytb-post", "label": "How to Summarize a YouTube Video Using AI-FLOW" } ] }, "gpt-vision": { "description": "Use GPT-4-o to analyze a picture.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/vision-demo.gif", "docUrls": [] }, "youtube_transcript_input": { "description": "Captures transcripts directly from YouTube API, allowing for further processing like translation, summarization, or keyword extraction.", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/summarize-ytb-post", "label": "How to Summarize a YouTube Video Using AI-FLOW" } ] }, "dalle-prompt": { "description": "Allows users to create detailed prompts to generate images using the DALL-E 3 model, combining creativity and AI for custom visual content. Please note that OpenAI only allow 5-7 images per minute. Don't forget to save your file; OpenAI host files for 1H.", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/text-to-image-processing", "label": "Image Generation in AI-FLOW" } ] }, "stable-diffusion-stabilityai-prompt": { "description": "Stable Diffusion SDXL model by Stability AI, offering quick and low cost image generation. Don't forget to save your file; files are available for 12 hours.", "docUrls": [] }, "merger-prompt": { "description": "Used for combining two outputs. Each output need to be used with his specific identifier that will be replaced dynamically e.g ${input-1} and ${input-2}. Use the buttons at the top of the node to insert the identifier automatically !", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/merge-demo.gif", "docUrls": [] }, "claude-anthropic-processor": { "description": "Processes inputs using Claude 3 by Anthropic, which can understand and generate responses based on the context provided by the user.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/claude-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/anthropic-claude-api", "label": "Access Claude 3 from Anthropic API through AI-FLOW" } ] }, "document-to-text-processor": { "description": "Converts various document formats into plain text, enabling text extraction for processing and analysis. This nodes supports .pdf, .txt, .json, .html, .csv.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/document-to-text-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/summarize-doc-post", "label": "How to Summarize Documents or Ask Questions Using AI-FLOW" } ] }, "openai-text-to-speech-processor": { "description": "Converts text to natural-sounding speech using OpenAI's advanced text-to-speech models, facilitating accessibility and multimedia applications. Don't forget to save your file, OpenAI host the file for 1H.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/tts-demo.gif", "docUrls": [] }, "stabilityai-generic-processor": { "description": "A versatile node capable of interfacing with StabilityAI API to perform various task such as remove background, search and replace and more. Don't forget to save your file; files are available for 12 hours. ", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/stabilityai-demo.gif", "docUrls": [] }, "stabilityai-stable-diffusion-3-processor": { "description": "Integrates the latest Stable Diffusion 3 capabilities for high-quality image generation. Don't forget to save your file; files are available for 12 hours.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/sd3-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/stable-diffusion-3-api", "label": "Access Stable Diffusion 3 API through AI-FLOW" } ] }, "file": { "description": "Handles the uploading, storage, and retrieval of files, supporting various file types for use within the system. This node does not extract file content. Files are available 12H.", "docUrls": [ { "url": "https://docs.ai-flow.net/blog/summarize-doc-post", "label": "How to Summarize Documents or Ask Questions Using AI-FLOW" } ] }, "ai-data-splitter": { "description": "Split an input into multiple outputs using two available modes: AI mode and Manual mode. In Manual mode, you must specify a separator. This can be useful for generating content based on a list of ideas or concepts. You can specify an estimated number of outputs to prepare your flow accordingly.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/splitter-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/split-input", "label": "Split input with AI" } ] }, "replicate": { "description": "A versatile node capable of interfacing with the Replicate API. Explore various model for text, image, audio, 3d model generation ! Replicate cost vary per model, and per time usage. Please not that less used models needs a warmup time, for this reason, first launch can be longer. Don't forget to save your file; files are available for 12 hours.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/replicate-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/replicate-node", "label": "Access Diverse AI Models through Replicate" } ] }, "transition": { "description": "Use this node to organize your flow. The transition node only transfer output to another node.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/transition-demo.gif", "docUrls": [] }, "display": { "description": "This resizeable node can be use to display every output at the size you wish. You can also use it as an intermidiary node.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/display-demo.gif", "docUrls": [] }, "deepseek-processor": { "description": "The DeepSeek node is designed to interact with the DeepSeek API, enabling you to access various V3 and R1 models." }, "generate-number-processor": { "description": "The Generate Number node is designed to generate a random number within a specified range." }, "openrouter-processor": { "description": "OpenRouter provides an OpenAI-compatible completion API to multiple models & providers. \n\nThis node is provided as a User Integration, to use it, please provide your OpenRouter API Key in the Secure Store." }, "http-get-processor": { "description": "Send a HTTP GET request with the specified headers." }, "gpt-image-processor": { "description": "The GPT Image node is designed to interact with the GPT Image API, enabling you to generate and edit images based on a prompt and reference images." } } ================================================ FILE: packages/ui/public/locales/en/tips.json ================================================ { "tips": [ { "title": "Getting started with AI-Flow", "description": "This guide will help you get started with AI-Flow, adding nodes, connecting them, customizing workspace.", "url": "https://docs.ai-flow.net/blog/getting-started-with-ai-flow/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/app-overview-r.png", "newFeature": true, "timeEstimated": "4min read" }, { "title": "Replicate Node Usage", "description": "Seamlessly Integrate Replicate API with AI-FLOW for AI workflow automation.", "url": "https://docs.ai-flow.net/blog/replicate-node/", "imgUrl": "https://docs.ai-flow.net/img/page-images/replicate-node/model-popup.png", "newFeature": true, "timeEstimated": "2min read" }, { "title": "How to Use Subflows", "description": "This feature allows you to create custom nodes based on your flows. ", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/subflow/", "imgUrl": "https://docs.ai-flow.net/img/page-images/api-builder/subflow-preview-3.png", "newFeature": true, "timeEstimated": "2min read" }, { "title": "How to Create Loops", "description": "This feature allows you loop on a Subflow. ", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/subflow-loop/", "imgUrl": "https://docs.ai-flow.net/img/page-images/api-builder/subflow-loop-4.png", "newFeature": true, "timeEstimated": "4min read" }, { "title": "StabilityAI API with AI-FLOW", "description": "This integration offer a versatile range of image processing capabilities.", "url": "https://docs.ai-flow.net/blog/stabilityai-api/", "imgUrl": "https://docs.ai-flow.net/img/blog-images/stabilityai.png", "timeEstimated": "2min read" }, { "title": "Accessing the API Builder View", "description": "This view allows you to monitor the current state of the API, learn how to use your API, and more.", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/builder-view/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "3min read" }, { "title": "Add Webhooks to your Flow", "description": "The Webhook Node is a powerful tool that allows you to send outputs as webhooks. ", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/webhooks/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "3min read" }, { "title": "Run Flow through API", "description": "Discover how to create and manage an API around a given Flow to integrate it seamlessly to other tools.", "url": "https://docs.ai-flow.net/docs/category/api-builder/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "2min read" }, { "title": "Full Documentation", "description": "The main page of AI-FLOW Documentation, accessible through https://docs.ai-flow.net/", "url": "https://docs.ai-flow.net/", "imgUrl": "https://docs.ai-flow.net/img/ai-flow-social-card.png" } ], "docAvailable": "Full documentation is available here : ", "tipsSection": "Tips" } ================================================ FILE: packages/ui/public/locales/en/tour.json ================================================ { "firstTimeHere": "First time here?", "discoverApp": "Unlock tips to make the most of our app in just 15 seconds!", "iKnowTheApp": "I know the app", "letsStart": "Let's start!", "welcomeToAIFLOW": "Welcome to AI-FLOW", "addNodesWithDragAndDrop": "Easily add nodes to your canvas with a simple drag & drop.", "dragAndDrop": "Drag and Drop", "addingNodes": "Adding Nodes", "runningANode": "Running a Node", "connectingNodes": "Connecting Nodes", "runEverything": "Run Everything", "exploringMoreModels": "Exploring More Models", "youveGotTheBasics": "You've got the basics!", "executeSingleNode": "You can execute a single node by clicking the run button.", "runNode": "Run Node", "handlesExplanation": "Blue handles are for inputs, and orange handles are for outputs. For GPT Nodes, inputs add context to your prompts.", "connectNodes": "Connect Nodes", "executeAllNodesDescription": "This button executes all nodes in your flow, overwriting previous outputs.", "replicateNodeDescription": "Expand your capabilities with the Replicate Node, providing access to a wide range of models for advanced use-cases.", "replicateNode": "Replicate Node", "checkHelpForAdvanced": "For advanced use-cases, check the Help section at the bottom left.", "configDescription": "Here you can add your API Keys to be able to use the app.", "config": "Config" } ================================================ FILE: packages/ui/public/locales/en/version.json ================================================ { "versionInfo": { "versionNumber": "v0.7.3", "description": "Discover the latest features added in version 0.7.3" }, "features": [ { "title": "Improved Web Extractor", "description": "You can now customized how data is extracted." }, { "title": "New Action: Help", "description": "Each node now includes a 'Help' action that enables you to learn how to use it." } ], "articles": [ { "title": "Generate Consistent Characters Using AI - Part 1", "url": "https://docs.ai-flow.net/blog/generate-consistent-characters-ai/" }, { "title": "How to automate story and image creation using AI - Part 2", "url": "https://docs.ai-flow.net/blog/automate-story-creation-2/" }, { "title": "How to use Documents in AI-FLOW", "url": "https://docs.ai-flow.net/blog/summarize-doc-post/" } ], "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/gif-v0.7.3.gif", "newVersionAvailable": "A new version is now available !", "newVersionDefaultMessage": "New features and bug fixes are available. To access them, please refresh your page.", "refresh": "Refresh" } ================================================ FILE: packages/ui/public/locales/fr/aiActions.json ================================================ { "Summary": "Résumé", "SpellCheck": "Vérification Orthographique", "VisualPrompt": "Prompt Visuelle", "ConstructiveCritique": "Critique Constructive", "SimpleExplanation": "Explication Simple", "Paraphrase": "Paraphrase", "SentimentAnalysis": "Analyse de Sentiment", "TextExtension": "Extension du Texte", "ClickToShowOutput": "Cliquez pour afficher le résultat" } ================================================ FILE: packages/ui/public/locales/fr/config.json ================================================ { "configurationTitle": "Configuration", "apiKeyDisclaimer": "Nous n'utilisons ni ne stockons vos clés API.", "openSourceDisclaimer": "Ce projet est open source.", "apiKeyRevokeReminder": "N'oubliez pas, vous pouvez révoquer vos clés à tout moment et en générer de nouvelles.", "closeButtonLabel": "Fermer", "validateButtonLabel": "Valider", "likeProjectPrompt": "Si vous aimez ce projet, vous pouvez ajouter une étoile sur:", "supportProjectPrompt": "Vous pouvez soutenir l'avenir du projet et nous contacter via", "Logout": "Se déconnecter", "sections.core": "Paramètres de base", "parameters.core.openai_api_key": "Clé API OpenAI", "parameters.core.stabilityai_api_key": "Clé API StabilityAI", "parameters.core.replicate_api_key": "Clé API Replicate", "parameters.extension.anthropic_api_key": "Clé API Anthropic", "sections.extension": "Extensions", "userTabLabel": "Paramètres utilisateur", "appParametersLabel": "Paramètres de l'application", "displayTabLabel": "Paramètres d'affichage", "nodesDisplayed": "Nœuds activés", "configUpdated": "Configuration mise à jour avec succès", "ShowMinimap": "Afficher la minimap", "UI": "Interface Utilisateur", "input": "Entrées", "models": "Modèles", "tools": "Outils", "parameters.extension.deepseek_api_key": "Clé API DeepSeek", "parameters.extension.openrouter_api_key": "Clé API OpenRouter", "incompleteLoadingPleaseRestart": "L’application n’a pas pu charger toutes les données. Veuillez redémarrer l’application." } ================================================ FILE: packages/ui/public/locales/fr/dialogs.json ================================================ { "attachNodeTitle": "Attacher un noeud", "attachNodeAction": "Attacher" } ================================================ FILE: packages/ui/public/locales/fr/flow.json ================================================ { "Flow": "Flow", "AddTab": "Ajouter un Flow", "ShowOnlyOutputs": "Afficher uniquement les résultats", "ShowOnlyParams": "Afficher uniquement les paramètres", "Prompt": "Prompt", "ApiKeyRequiredMessage": "Veuillez fournir votre clé API dans les paramètres de configuration pour exécuter correctement votre flow.", "ClickToShowOutput": "Cliquez pour afficher le résultat", "RoleInitPrompt": "Indiquez le rôle que vous souhaitez que l'IA adopte lors de vos prochaines interactions. Par exemple, 'Comporte toi comme un Critique Littéraire'.", "EnterURL": "Extracteur Web", "YoutubeTranscriptNodeName": "Transcription Youtube", "URLPlaceholder": "Saisissez une URL", "Input": "Entrées", "InputImage": "Image", "InputPlaceholder": "Saisissez votre texte ici pour qu'il serve d'entrée à d'autres nœuds", "InputImagePlaceholder": "Saisissez l'URL vers l'image que vous souhaitez utiliser", "DALLE": "DALL-E", "ClickToShowImageOutput": "Cliquez pour afficher la sortie d'image", "JsonView": "Vue JSON", "TopologicalView": "Résultats", "currentNodeView": "Noeud selectionné", "Upload": "Importer", "Download": "Télécharger", "Text": "Texte", "URL": "URL", "YoutubeVideo": "Transcription Youtube", "Models": "Modèles", "GPT": "Modèle GPT", "GPTPrompt": "GPT Prompt", "NoContextPrompt": "GPT Prompt", "PromptPlaceholder": "Entrez votre prompt ici, par exemple 'Crée un thread Twitter basé sur les données que je t'ai envoyées.'", "MergePromptPlaceholder": "Entrez votre texte pour la fusion en utilisant les constantes ${input-1} et ${input-2}. Vous pouvez ajouter du texte supplémentaire, par exemple : \n Répond à ${input-1} en tenant compte de ${input-2}.", "VisionPromptPlaceholder": "Entrez votre prompt ici pour analyser l'image en entrée. Par exemple 'Crée une description de cette image'", "VisionImageURLPlaceholder": "Saisissez l'URL vers l'image que vous souhaitez utiliser", "DallEPromptPlaceholder": "Entrez votre prompt ici, par exemple 'Un chien et un chat jouant dans le désert'", "DataSplitter": "Data Splitter", "ImageGeneration": "Génération d'image", "AdvancedSection": "Avancé", "AiAction": "Action IA", "LLMPrompt": "GPT", "AiDataSplitter": "Data Splitter", "MergerNode": "Fusion de Texte", "ReplicateModel": "Replicate", "inputHelp": "Ce noeud sert à saisir du texte.", "inputImageHelp": "Ce noeud permet de visualiser une image via une URL.", "urlInputHelp": "Saisissez une URL valide et le noeud récupérera les données de cette URL.", "youtubeTranscriptHelp": "Ce noeud récupère les sous titres d'une vidéo Youtube à partir de son URL", "gptHelp": "Ce noeud permet de configurer un modèle GPT, de lui préciser son rôle, et les données sur lesquelles il se basera pour répondre.", "gptPromptHelp": "Ce noeud permet d'interroger un modèle GPT. Il partage son contexte avec les autres noeuds connectés au modèle.", "noContextPromptHelp": "Ce noeud permet d'interroger GPT sans contexte, juste à partir de données d'entrées et d'un prompt. Pas besoin de le connecter à un modèle.", "stableDiffusionPromptHelp": "Ce noeud utilise le modèle Stable Diffusion pour générer des images à partir d'une description textuelle.", "stableVideoDiffusionPromptHelp": "Ce noeud utilise Replicate pour lancer le modèle Stable Video Diffusion.", "aiActionPromptHelp": "Ce noeud permet de réaliser des actions simples avec GPT-4 sans préciser de prompt.", "llmPromtHelp": "Ce noeud permet de traiter une prompt avec GPT-3.5 ou GPT-4", "dataSplitterHelp": "Ce noeud sert à diviser les données en plusieurs parties. Vous pouvez spécifier en amont combien de parties vous voulez créer. Vous pouvez également le lancer unitairement pour qu'il trouve le nombre exact de sorties à générer.", "replicateHelp": "Ce nœud utilise Replicate pour donner accès à un grand nombre de modèles.", "mergerPromptHelp": "Ce nœud vous permet de fusionner 2 entrées.", "gptVisionPromptHelp": "Ce nœud utilise GPT-4 Vision et prend une URL d'image comme entrée.", "socketConnectionLost": "La connexion a été perdue. \n\n Vous pouvez réessayer plus tard ou installer l'application localement pour éviter ce type de problème à l'avenir.", "ClickToSelectModel": "Cliquez pour sélectionner un modèle", "Or": "OU", "EnterModelNameDirectly": "Entrez directement le nom du modèle", "Load": "Charger", "LoadMore": "Charger plus", "SpotlightModels": "Modèles Vedettes", "AllModels": "Tous les Modèles", "EdgeType": "Type d'arête", "CannotChangeTabWhileRunning": "Vous ne pouvez pas changer d'onglet quand un lancement est en cours.", "Transition": "Transition", "transitionHelp": "Ce noeud sert uniquement à organiser le flow.", "MissingFieldsMessage": "Des champs nécessaires sont manquants", "Node": "Noeud", "MissingFields": "Champ manquant", "CannotDeleteLastFlow": "Impossible de supprimer le dernier Flow", "HideSidebar": "Cacher la barre", "ShowSidebar": "Afficher la barre", "File": "Fichier", "EnterUrlToDesiredFile": "Entrez l'URL", "fileUploadHelp": "Utilisez ce noeud pour charger un fichier", "llmPromptHelp": "Ce noeud permet d'envoyer des prompts à GPT-3.5 ou GPT-4", "Output": "Résultat", "Inputs": "Entrées", "Parameters": "Paramètres", "Duplicate": "Dupliquer", "OpeninSidepane": "Ouvrir dans le bandeau", "ClearOutput": "Supprimer le résultat", "RemoveNode": "Supprimer le noeud", "ExpiredURL": "URL expirée", "NoNodeSelected": "Aucun nœud sélectionné pour le moment.", "ClickOnNodeToSelectIt": "Veuillez cliquer sur un nœud pour le sélectionner.", "Field": "Champ", "DragAndDropNodes": "Glissez et déposez les nœuds pour les ajouter.", "CopiedToClipboard": "Copié dans le presse-papiers.", "DocumentToText": "Document vers Texte", "documentToTextHelp": "Convertir un fichier .pdf, .txt, .csv, .json, .html en texte simple", "TextToSpeech": "Texte vers Audio", "textToSpeechHelp": "Convertir un texte en fichier audio en utilisant le modèle tts d'OpenAI", "error.upload_failed": "Echec de l'upload. Vérifiez votre configuration pour pouvoir activer l'upload.", "InputTextPlaceholder": "Entrez votre texte ici", "DownloadFile": "Télécharger le fichier", "FileUploaded": "Fichier hébergé", "GenericPromptPlaceholder": "Entrez vos instructions ici", "GenericNegativePromptPlaceholder": "Entrez vos instructions négatives ici", "EnterCustomName": "Nom personalisé :", "NodeColor": "Couleur du noeud", "ChangeName": "Changer le nom", "RemoveFlow": "Supprimer le flow", "HideHint": "Cacher", "TextDocumentHint": "Veuillez noter que ce nœud fournit les fichiers uniquement sous forme d'URLs. Pour utiliser un document (.pdf, .txt) en format texte, envisagez d'utiliser le nœud 'Document-en-Texte'.", "Display": "Affichage", "displayHelp": "Ce noeud redimensionable permet d'afficher du contenu.", "Validate": "Valider", "AI": "IA", "Separator": "Séparateur", "ClaudeAnthropic": "Claude", "claudeAnthropichHelp": "Ce noeud utilise le modèle Claude d'Anthropic pour traiter des instructions textuelles.", "noDataAvailableForThisNode": "Pas de données disponibles pour ce noeud.", "learnMore": "En apprendre plus :", "Help": "Aide", "cookiesConsentLabelPlaceholder": "Accepter tout", "cookiesConsentLabelHelp": "Pour certaines pages, nous devons cliquer sur le bouton de consentement aux cookies pour accéder aux données. Cette instruction aide à localiser le bouton.", "EditTextContent": "Editer le texte", "ShowCoordinates": "Afficher les coordonnées", "ShowNodesConfig": "Afficher les configurations des noeuds", "DeleteAll": "Tout supprimer", "DeleteOutputs": "Supprimer les résultats", "ReplaceText": "Remplacer Texte", "ReplaceTextInputPlaceholder": "Entrez le texte complet où le terme sera remplacé.", "ReplaceTextSearchPlaceholder": "Entrez le terme ou le motif regex à remplacer.", "ReplaceTextReplacePlaceholder": "Entrez le terme de remplacement.", "replaceTextNodeHelp": "Utilisez ce nœud pour rechercher et remplacer un texte spécifique ou des motifs dans l'entrée.", "openaio1Help": "Des modèles de langage avancés formés pour le raisonnement complexe, excellant dans les défis scientifiques, mathématiques et de programmation.", "ContextPlaceholder": "Contexte additionnel qui sera utilisé pour répondre au prompt", "deepSeekHelp": "Accédez aux LLMs DeepSeek via ce noeud.", "openRouterHelp": "OpenRouter donne accès à plusieurs LLMs et providers. Ce noeud nécessite de fournir une clé API.", "Generate Number": "Générer un nombre", "generateNumberHelp": "Génère un nombre aléatoire", "httpGetProcessorURLPlaceholder": "Enter the URL to request", "httpGetProcessorURLDescription": "The URL that the HTTP GET request will be sent to.", "httpGetProcessorHeadersPlaceholder": "Enter headers in JSON format", "httpGetProcessorHeadersDescription": "The headers to include in the HTTP GET request.", "httpGetProcessorHelp": "Send an HTTP GET request with the specified headers.", "gptImageHelp": "Generate or Edit an image using GPT Image", "gptImageMaskDescription": "You can provide a mask to indicate where the image should be edited. You can use the prompt to describe the full new image, not just the erased area. If you provide multiple input images, the mask will be applied to the first image.", "dallEDeprecated": "Most recent OpenAI models are now available via the new GPT Image node and are superior to DALL-E. DALL-E remains available if needed.", "TTSInstructionPlaceholder": "Ex : Parlez avec un ton joyeux et positif.", "TTSInstructionDescription": "Invitez le modèle à contrôler les aspects de la parole (accent, émotion, intonation, vitesse, ton, ...)", "PopularModels": "Modèles Populaires", "removeBackgroundDescription": "Supprimez l'arrière-plan d'une image en utilisant l'API StabilityAI.", "upscaleFastDescription": "Augmenter la résolution d'une image avec l'API StabilityAI", "fluxDescription": "Générez une image en utilisant le modèle FLUX.", "fluxKontextDescription": "Un modèle d’édition d’image basé sur le texte à la pointe de la technologie, offrant des résultats de haute qualité, respectant fidèlement les instructions des prompts et garantissant une transformation cohérente des images via le langage naturel.", "faceswapDescription": "Échangez des visages entre des images de manière transparente, permettant des remplacements faciaux réalistes et précis.", "removeBgDescription": "Supprimez le fond d'une image avec le modèle lucataco/remove-bg sur Replicate", "upscaleDescription": "Modèle Real-ESRGAN pour augmenter la résolution d'une image", "moondreamDescription": "Moondream est un modèle de vision qui répond aux commandes concernant une image donnée.", "llamaDescription": "Le modèle de langage phare de Meta, doté de 405 milliards de paramètres, affiné pour la complétion des discussions.", "imagenDescription": "Google Imagen produit des images époustouflantes, détaillées et précises.", "recraftSVGDescription": "Recraft SVG propose une génération d'images vectorielles avancée.", "recraftDescription": "Recraft V3 produit des images époustouflantes, détaillées et précises.", "video01Description": "Video-01 offre une génération dynamique de vidéos.", "video01LiveDescription": "Video-01-Live offre une génération dynamique de vidéos, idéale pour l'animation 2D.", "klingDescription": "Kling propose des solutions robustes de génération vidéo et d'animation, disponibles en versions professionnelle et standard.", "veo3Description": "Modèle phare de Google, Veo 3, de texte à vidéo, avec audio" } ================================================ FILE: packages/ui/public/locales/fr/nodeHelp.json ================================================ { "input-text": { "description": "Le nœud de texte peut être utilisé pour transférer une entrée de texte à d'autres nœuds.", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/Input-nodes", "label": "Ajouter une entrée dans AI-FLOW" } ] }, "url_input": { "description": "Récupère le contenu textuel d'une page web à partir d'une URL.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/web-extractor-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/Input-nodes#url", "label": "Ajouter une entrée dans AI-FLOW" } ] }, "llm-prompt": { "description": "Traite les entrées en utilisant les modèles GPT d'OpenAI, qui peuvent comprendre et générer des réponses en fonction du contexte fourni par l'utilisateur.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/gpt-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/summarize-doc-post", "label": "Comment Résumer des Documents ou Poser des Questions en Utilisant AI-FLOW" }, { "url": "https://docs.ai-flow.net/fr/blog/summarize-ytb-post", "label": "Comment Résumer une Vidéo YouTube en Utilisant AI-FLOW" } ] }, "gpt-vision": { "description": "Utilise GPT-4 Vision pour analyser une image.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/vision-demo.gif", "docUrls": [] }, "youtube_transcript_input": { "description": "Capture les transcriptions directement depuis l'API de YouTube, permettant un traitement ultérieur tel que la traduction, la résumé, ou l'extraction de mots clés.", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/summarize-ytb-post", "label": "Comment Résumer une Vidéo YouTube en Utilisant AI-FLOW" } ] }, "dalle-prompt": { "description": "Permet aux utilisateurs de créer des descriptions détaillées pour générer des images à l'aide du modèle DALL-E 3, combinant créativité et IA pour un contenu visuel personnalisé. Veuillez noter qu'OpenAI ne permet que 5-7 images par minute. N'oubliez pas de sauvegarder votre fichier; OpenAI héberge les fichiers pendant 1H.", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/text-to-image-processing", "label": "Génération d'image dans AI-FLOW" } ] }, "stable-diffusion-stabilityai-prompt": { "description": "Modèle Stable Diffusion SDXL par Stability AI, offrant une génération d'images rapide et à faible coût. N'oubliez pas de sauvegarder votre fichier ; les fichiers sont disponibles pendant 12 heures.", "docUrls": [] }, "merger-prompt": { "description": "Utilisé pour combiner deux sorties. Chaque sortie doit être utilisée avec son identifiant spécifique qui sera remplacé dynamiquement, par exemple ${input-1} et ${input-2}. Utilisez les boutons en haut du nœud pour insérer automatiquement les identifiants.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/merge-demo.gif", "docUrls": [] }, "claude-anthropic-processor": { "description": "Traite les entrées en utilisant Claude 3 par Anthropic, qui peut comprendre et générer des réponses en fonction du contexte fourni par l'utilisateur.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/claude-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/anthropic-claude-api", "label": "Accédez à Claude 3 via l'API d'Anthropic grâce à AI-FLOW" } ] }, "document-to-text-processor": { "description": "Convertit divers formats de documents en texte brut, permettant l'extraction de texte pour le traitement et l'analyse. Ce nœud prend en charge les formats .pdf, .txt, .json, .html, .csv.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/document-to-text-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/summarize-doc-post", "label": "Comment Résumer des Documents ou Poser des Questions en Utilisant AI-FLOW" } ] }, "openai-text-to-speech-processor": { "description": "Convertit le texte en parole naturelle à l'aide des modèles avancés de synthèse vocale d'OpenAI, facilitant l'accessibilité et les applications multimédia. N'oubliez pas de sauvegarder votre fichier, OpenAI héberge le fichier pendant 1H.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/tts-demo.gif", "docUrls": [] }, "stabilityai-generic-processor": { "description": "Un nœud polyvalent capable d'interfacer avec l'API de StabilityAI pour effectuer diverses tâches telles que supprimer l'arrière-plan, rechercher et remplacer, et plus encore. N'oubliez pas de sauvegarder votre fichier ; les fichiers sont disponibles pendant 12 heures.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/stabilityai-demo.gif", "docUrls": [] }, "stabilityai-stable-diffusion-3-processor": { "description": "Intègre les dernières capacités de Stable Diffusion 3 pour une génération d'images de haute qualité. N'oubliez pas de sauvegarder votre fichier ; les fichiers sont disponibles pendant 12 heures.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/sd3-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/stable-diffusion-3-api", "label": "Access Stable Diffusion 3 API through AI-FLOW" } ] }, "file": { "description": "Permet d'héberger un fichier et de retourner une URL permettant d'y accéder. Supporte divers types de fichiers pour une utilisation au sein du système. Ce nœud n'extrait pas le contenu des fichiers. Les fichiers sont disponibles pendant 12H.", "docUrls": [ { "url": "https://docs.ai-flow.net/fr/blog/summarize-doc-post", "label": "Comment Résumer des Documents ou Poser des Questions en Utilisant AI-FLOW" } ] }, "ai-data-splitter": { "description": "Divise une entrée en plusieurs sorties en utilisant deux modes disponibles : mode AI et mode Manuel. En mode Manuel, vous devez spécifier un séparateur. Cela peut être utile pour générer du contenu basé sur une liste d'idées ou de concepts. Vous pouvez spécifier un nombre estimé de sorties pour préparer votre flux en conséquence.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/splitter-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/split-input", "label": "Séparer une entrée avec l'IA" } ] }, "replicate": { "description": "Un nœud polyvalent capable d'interfacer avec l'API Replicate. Explorez divers modèles pour la génération de texte, d'images, d'audio, de modèles 3D. Les fichiers en sortie sont accessibles 12H.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/replicate-demo.gif", "docUrls": [ { "url": "https://docs.ai-flow.net/docs/nodes-presentation/replicate-node", "label": "Accéder a divers modèles IA via Replicate" } ] }, "transition": { "description": "Utilisez ce nœud pour organiser votre flux. Le nœud de transition ne transfère la sortie qu'à un autre nœud.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/transition-demo.gif", "docUrls": [] }, "display": { "description": "Ce nœud redimensionnable peut être utilisé pour afficher chaque sortie à la taille que vous souhaitez. Vous pouvez également l'utiliser comme un nœud intermédiaire.", "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/node-help-img/display-demo.gif", "docUrls": [] }, "deepseek-processor": { "description": "Le nœud DeepSeek est conçu pour interagir avec l'API DeepSeek, vous permettant d'accéder à différents modèles tels que V3 et R1." }, "openrouter-processor": { "description": "OpenRouter donne accès à plusieurs LLMs et providers. Ce noeud nécessite de fournir une clé API." }, "generate-number-processor": { "description": "Le nœud « Générer un nombre » est conçu pour générer un nombre aléatoire dans une plage spécifiée." }, "http-get-processor": { "description": "Ce noeud permet d'envoyer une requête HTTP GET sur l'URL spécifiée, avec les headers souhaités." }, "gpt-image-processor": { "description": "Le noeud GPT Image vous permet de générer et de modifier des images à partir d'une instruction et d'images de référence. " } } ================================================ FILE: packages/ui/public/locales/fr/tips.json ================================================ { "tips": [ { "title": "Bien débuter sur AI-Flow", "description": "Ce guide vous montrera l'essentiel pour bien débuter.", "url": "https://docs.ai-flow.net/fr/blog/getting-started-with-ai-flow/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/app-overview-r.png", "newFeature": true, "timeEstimated": "4min de lecture" }, { "title": "Utilisation du noeud Replicate", "description": "Intégrez l'API Replicate avec AI-FLOW.", "url": "https://docs.ai-flow.net/fr/blog/replicate-node/", "imgUrl": "https://docs.ai-flow.net/img/page-images/replicate-node/model-popup.png", "newFeature": true, "timeEstimated": "2min de lecture" }, { "title": "Comment utiliser les sous-flow", "description": "Cette fonctionnalité vous permet de créer des nœuds personnalisés basés sur vos flows.", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/subflow/", "imgUrl": "https://docs.ai-flow.net/img/page-images/api-builder/subflow-preview-3.png", "newFeature": true, "timeEstimated": "2min de lecture" }, { "title": "Comment créer des boucles", "description": "Cette fonctionnalité vous permet d'itérer sur un sous-flow.", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/subflow-loop/", "imgUrl": "https://docs.ai-flow.net/img/page-images/api-builder/subflow-loop-4.png", "newFeature": true, "timeEstimated": "4min read" }, { "title": "StabilityAI avec AI-FLOW", "description": "Cette intégration offre une gamme polyvalente de capacités de traitement d'image.", "url": "https://docs.ai-flow.net/fr/blog/stabilityai-api/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-sd3.png", "timeEstimated": "3min de lecture" }, { "title": "Vue API Builder", "description": "Cette vue vous permet de surveiller l'état actuel de l'API, d'apprendre à utiliser votre API, et plus encore.", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/builder-view/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "3min de lecture" }, { "title": "Ajouter des Webhooks à vos Flows", "description": "Le nœud Webhook est un outil puissant qui vous permet d'envoyer des sorties sous forme de webhooks.", "url": "https://docs.ai-flow.net/docs/pro-features/api-builder/webhooks/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "2min de lecture" }, { "title": "Lancer un Flow via l'API", "description": "Découvrez comment créer et gérer une API autour d'un Flow donné pour l'intégrer parfaitement à d'autres outils.", "url": "https://docs.ai-flow.net/docs/category/api-builder/", "imgUrl": "https://docs.ai-flow.net/img/blog-card-images/blog-api-builder-1.png", "newFeature": true, "timeEstimated": "2min de lecture" }, { "title": "Documentation Complète", "description": "La page principale de la documentation AI-FLOW, accessible via https://docs.ai-flow.net/", "url": "https://docs.ai-flow.net/", "imgUrl": "https://docs.ai-flow.net/img/ai-flow-social-card.png" } ], "docAvailable": "La documentation complète est disponible ici : ", "tipsSection": "Astuces" } ================================================ FILE: packages/ui/public/locales/fr/tour.json ================================================ { "firstTimeHere": "Première visite ?", "discoverApp": "Découvrez des astuces pour profiter pleinement de notre application en moins de 15 secondes !", "iKnowTheApp": "Je connais l'application", "letsStart": "Commençons !", "welcomeToAIFLOW": "Bienvenue sur AI-FLOW", "addNodesWithDragAndDrop": "Ajoutez facilement des nœuds à votre canevas avec un simple glisser-déposer.", "dragAndDrop": "Glisser-Déposer", "addingNodes": "Ajouter un Nœud", "runningANode": "Exécuter un Nœud", "connectingNodes": "Connecter des Nœuds", "runEverything": "Tout Exécuter", "exploringMoreModels": "Explorer Plus de Modèles", "youveGotTheBasics": "Vous avez les bases !", "executeSingleNode": "Vous pouvez exécuter un seul nœud en cliquant sur le bouton d'exécution.", "runNode": "Exécuter un Nœud", "handlesExplanation": "Les poignées bleues sont pour les entrées, et les poignées oranges pour les sorties. Pour les Nœuds GPT, les entrées ajoutent du contexte à vos invites.", "connectNodes": "Connecter les Nœuds", "executeAllNodesDescription": "Ce bouton exécute tous les nœuds dans votre flux, en écrasant les sorties précédentes.", "replicateNodeDescription": "Étendez vos capacités avec le Nœud Replicate, offrant un accès à une large gamme de modèles pour des cas d'usage avancés.", "replicateNode": "Replicate", "checkHelpForAdvanced": "Pour des cas d'usage avancés, consultez la section Aide en bas à gauche.", "configDescription": "Vous pouvez ajouter vos clés APIs via ce menu pour utiliser l'application.", "config": "Configuration" } ================================================ FILE: packages/ui/public/locales/fr/version.json ================================================ { "versionInfo": { "versionNumber": "v0.7.3", "description": "Voici les nouveautés de la v0.7.3" }, "features": [ { "title": "Amélioration de l'extracteur Web", "description": "Vous pouvez désormais mieux cibler l'extraction de données." }, { "title": "Nouvelle action : Aide", "description": "Chaque noeud possède désormais une action 'Aide' qui vous permet de découvrir comment l'utiliser." } ], "articles": [ { "title": "Générer des Personnages Cohérents avec l'IA - Partie 1", "url": "https://docs.ai-flow.net/fr/blog/generate-consistent-characters-ai/" }, { "title": "Comment automatiser la création d'histoires et d'images à l'aide de l'IA - Partie 2", "url": "https://docs.ai-flow.net/fr/blog/automate-story-creation-2/" }, { "title": "Comment Utiliser des Documents dans AI-FLOW", "url": "https://docs.ai-flow.net/fr/blog/summarize-doc-post/" } ], "imageUrl": "https://ai-flow-public-assets.s3.eu-west-3.amazonaws.com/gif-v0.7.3.gif", "newVersionAvailable": "Une nouvelle version est maintenant disponible !", "newVersionDefaultMessage": "De nouvelles fonctionnalités et des corrections de bugs sont disponibles, pour y accéder, veuillez rafraîchir votre page.", "refresh": "Rafraîchir" } ================================================ FILE: packages/ui/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: packages/ui/public/samples/intro.json ================================================ [ { "inputs": [], "name": "3jexlwros#llm-prompt", "processorType": "llm-prompt", "model": "gpt-4o", "x": -1130.048690482733, "y": -885.266525660136 } ] ================================================ FILE: packages/ui/public/site.webmanifest ================================================ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} ================================================ FILE: packages/ui/src/App.tsx ================================================ import { useContext, useEffect, useMemo, useState } from "react"; import FlowTabs, { FlowTab } from "./layout/main-layout/AppLayout"; import { ThemeContext } from "./providers/ThemeProvider"; import { DndProvider } from "react-dnd"; import { MultiBackend } from "react-dnd-multi-backend"; import { HTML5toTouch } from "rdndmb-html5-to-touch"; import { AppTour } from "./components/tour/AppTour"; import { VisibilityProvider } from "./providers/VisibilityProvider"; import { Tooltip } from "react-tooltip"; import { loadExtensions } from "./nodes-configuration/nodeConfig"; import { loadAllNodesTypes } from "./utils/mappings"; import { loadParameters } from "./components/popups/config-popup/parameters"; import { SocketProvider } from "./providers/SocketProvider"; import { getAllTabs } from "./services/tabStorage"; import { convertJsonToFlow } from "./utils/flowUtils"; import { UserMessage } from "./components/popups/UserMessagePopup"; import { useTranslation } from "react-i18next"; interface AppProps { onLoadingComplete: () => void; } const App = ({ onLoadingComplete }: AppProps) => { const { dark } = useContext(ThemeContext); const [runTour, setRunTour] = useState(false); const [configLoaded, setConfigLoaded] = useState(false); const [showApp, setShowApp] = useState(false); const [allTabs, setAllTabs] = useState([]); const [fixedAlert, setFixedAlert] = useState(null); const { t } = useTranslation("config"); const [appMounted, setComponentsMounted] = useState(false); useEffect(() => { if (dark) { document.body.classList.add("dark-theme"); } else { document.body.classList.remove("dark-theme"); } }, [dark]); useEffect(() => { loadAppData(); }, []); useEffect(() => { if (showApp) { setComponentsMounted(true); } }, [showApp]); const loadIntroFile = async () => { const firstVisit = localStorage.getItem("firstVisit") !== "false"; const savedFlowTabs = localStorage.getItem("flowTabs"); if (firstVisit && !savedFlowTabs) { try { const response = await fetch("/samples/intro.json"); if (!response.ok) { throw new Error("Failed to fetch intro file"); } const jsonData = await response.json(); const defaultTab: FlowTab = convertJsonToFlow(jsonData); localStorage.setItem("firstVisit", "false"); return [defaultTab]; } catch (error) { console.error("Cannot load sample file :", error); } } return []; }; async function loadAppData() { try { await loadParameters(); await loadExtensions(); const defaultTabs = await loadIntroFile(); const allTabs = await getAllTabs(); if (allTabs.length === 0) { allTabs.push(...defaultTabs); } setAllTabs(allTabs); } catch (error) { console.error("Failed to load app data:", error); console.error("Default parameters will be loaded"); setFixedAlert({ content: t("incompleteLoadingPleaseRestart"), }); } finally { loadAllNodesTypes(); setConfigLoaded(true); setShowApp(true); onLoadingComplete(); } } return ( <> {configLoaded && (
{fixedAlert && (
{fixedAlert.content}
)} {appMounted && runTour && ( )}
)} ); }; export default App; ================================================ FILE: packages/ui/src/Main.tsx ================================================ import React, { useState } from "react"; import App from "./App"; import LoadingScreen from "./components/LoadingScreen"; const Main = () => { const [initialLoading, setInitialLoading] = useState(true); const handleLoadingComplete = () => { setInitialLoading(false); }; return ( <> {initialLoading && } ); }; export default Main; ================================================ FILE: packages/ui/src/api/cache/cacheManager.ts ================================================ import { isCacheEnabled } from "../../config/config"; interface CacheItem { data: T; ttl?: number; timestamp: number; } const DEFAULT_TTL = 3600 * 1000; // 1 hour const DEFAULT_NB_ELEMENTS_TO_REMOVE = 5; const DISPENSABLE_CACHE_PREFIX = "dispensable_cache"; export function generateCacheKey(functionName: string, ...args: any[]): string { const argsKey = JSON.stringify(args); return `${functionName}:${argsKey}`; } export function setCache(key: string, data: any, ttl?: number) { if (!isCacheEnabled()) return; const item = { data, ttl, timestamp: Date.now(), }; try { localStorage.setItem(key, JSON.stringify(item)); } catch (err: any) { if (err.code == 22 || err.code == 1014) { clearOldCacheItems(); localStorage.setItem(key, JSON.stringify(item)); } else { throw new Error(err.message); } } } export function getCache(key: string): T | undefined { if (!isCacheEnabled()) return; const itemStr = localStorage.getItem(key); if (!itemStr) return; const item = JSON.parse(itemStr) as CacheItem; const now = Date.now(); const ttl = item.ttl ?? DEFAULT_TTL; if (now - item.timestamp > ttl) { localStorage.removeItem(key); return; } return item.data; } function clearOldCacheItems() { const keys = Object.keys(localStorage); const items = keys .filter((key) => key.includes(DISPENSABLE_CACHE_PREFIX)) .map((key) => ({ key, data: JSON.parse(localStorage.getItem(key) ?? ""), })) .sort((a, b) => a.data.timestamp - b.data.timestamp); items.forEach((item, index) => { if (index <= DEFAULT_NB_ELEMENTS_TO_REMOVE) { localStorage.removeItem(item.key); } }); } ================================================ FILE: packages/ui/src/api/cache/withCache.ts ================================================ import { generateCacheKey, getCache, setCache } from "./cacheManager"; type AsyncFunction = (...args: T) => Promise; type Params = T extends (...args: infer U) => any ? U : never; interface CacheOptions { ttl: number; key?: string; } async function withCache( fn: AsyncFunction, options: CacheOptions, ...args: Params> ): Promise; async function withCache( fn: AsyncFunction, ...args: Params> ): Promise; async function withCache( fn: AsyncFunction, ...args: | Params> | [CacheOptions, ...Params>] ): Promise { let options: CacheOptions | undefined = undefined; let parameters: Params>; if (args.length > 0 && typeof args[0] === "object" && "ttl" in args[0]) { options = args.shift() as CacheOptions; parameters = args as Params>; } else { parameters = args as Params>; } let cacheKey = options?.key; if (cacheKey === undefined) { cacheKey = generateCacheKey(fn.name, ...parameters); } let cachedResult = getCache(cacheKey); if (cachedResult !== undefined) { return cachedResult; } const result = await fn(...parameters); setCache(cacheKey, result); return result; } export default withCache; ================================================ FILE: packages/ui/src/api/client.ts ================================================ import axios from "axios"; import { getRestApiUrl } from "../config/config"; const apiClient = axios.create({ baseURL: getRestApiUrl(), headers: { "Content-type": "application/json", }, }); export default apiClient; ================================================ FILE: packages/ui/src/api/nodes.ts ================================================ import client from "./client"; export async function getNodeExtensions() { let response; try { response = await client.get(`/node/extensions`); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data?.extensions; } export async function getDynamicConfig(processorType: string, data: any) { let response; const dataToSend = { processorType, data, }; try { response = await client.post(`/node/extensions/dynamic`, dataToSend); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data; } export async function getModels(providerName: string) { let response; try { response = await client.get(`/node/openapi/${providerName}/models`); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data; } export async function getModelConfig(providerName: string, id: string) { let response; try { response = await client.get(`/node/openapi/${providerName}/config/${id}`); return response.data; } catch (error) { console.error("Error fetching configuration:", error); throw error; } } ================================================ FILE: packages/ui/src/api/parameters.ts ================================================ import client from "./client"; export async function getParameters() { let response; try { response = await client.get(`/parameters`); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data; } ================================================ FILE: packages/ui/src/api/replicateModels.ts ================================================ import { Config } from "../utils/openAPIUtils"; import client from "./client"; interface GetCollectionModelsResponse { models: any; cursor: string; } export async function getCollections() { let response; try { response = await client.get("/node/collections"); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data.results; } export async function getPublicModels(cursor?: string) { let response; try { response = await client.get("/node/models", { params: { cursor: cursor, }, }); } catch (error) { console.error("Error fetching configuration:", error); throw error; } const newCursor = response.data?.public.next ? response.data?.public.next.split("?cursor=")[1] : ""; return { models: response.data.public.results, cursor: newCursor, } as GetCollectionModelsResponse; } export async function getHighlightedModels() { let response; try { response = await client.get("/node/models"); } catch (error) { console.error("Error fetching configuration:", error); throw error; } return response.data.highlighted; } export async function getCollectionModels( collectionName: string, cursor?: string, ) { let response; try { response = await client.get(`/node/collections/${collectionName}`, { params: { cursor: cursor, }, }); } catch (error) { console.error("Error fetching configuration:", error); throw error; } const models = response.data.models; const newCursor = response.data?.next ? response.data?.next.split("?cursor=")[1] : ""; return { models, cursor: newCursor } as GetCollectionModelsResponse; } export async function getModelConfig(model: string, processorType: string) { let response; try { response = await client.get(`/node/replicate/config/${model}`, { params: { processorType: processorType, }, }); return response.data as Config; } catch (error) { console.error("Error fetching configuration:", error); throw error; } } ================================================ FILE: packages/ui/src/api/uploadFile.ts ================================================ import axios, { AxiosProgressEvent } from "axios"; import client from "./client"; export async function getUploadAndDownloadUrl(filename?: string) { try { const data = { filename }; const response = await client.get("/upload", { params: data }); return response.data; } catch (error) { console.error("Error while trying to get upload link :", error); throw error; } } export async function uploadWithS3Link(s3UploadData: any, file: File) { const config = { onUploadProgress: (progressEvent: AxiosProgressEvent) => { if (!progressEvent.total) return; const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total, ); console.log(`Upload progress: ${percentCompleted}%`); }, }; try { const url = s3UploadData.url; const fields = s3UploadData.fields; const formData = new FormData(); Object.keys(fields).forEach((key) => { formData.append(key, fields[key]); }); formData.append("file", file); await axios.post(url, formData, config); } catch (error) { console.error("Error uploading file :", error); throw error; } } ================================================ FILE: packages/ui/src/components/Flow.tsx ================================================ import { useState, useCallback, useMemo, useEffect, useRef, useImperativeHandle, Ref, forwardRef, } from "react"; import { Node, Edge, OnNodesChange, OnEdgesChange, OnConnect, applyNodeChanges, applyEdgeChanges, addEdge, Connection, ReactFlowInstance, } from "reactflow"; import "reactflow/dist/style.css"; import SideBar from "./bars/Sidebar"; import { NodeProvider } from "../providers/NodeProvider"; import { MiniMapStyled, ReactFlowStyled } from "./nodes/Node.styles"; import UserMessagePopup, { MessageType, UserMessage, } from "./popups/UserMessagePopup"; import { getAllNodeWithEaseOut } from "../utils/mappings"; import { useDrop } from "react-dnd"; import { useSocketListeners } from "../hooks/useFlowSocketListeners"; import ButtonEdge from "./edges/buttonEdge"; import { createNewNode } from "../utils/nodeUtils"; import { FlowOnCurrentNodeRunningEventData, FlowOnErrorEventData, FlowOnProgressEventData, } from "../sockets/flowEventTypes"; import { useVisibility } from "../providers/VisibilityProvider"; import { FlowMetadata } from "../layout/main-layout/AppLayout"; export interface FlowProps { nodes: Node[]; edges: Edge[]; metadata: FlowMetadata; onFlowChange: (nodes: Node[], edges: Edge[], metadata: FlowMetadata) => void; onUpdateMetadata?: (metadata: FlowMetadata) => void; showOnlyOutput?: boolean; isRunning: boolean; onRunChange: (isRunning: boolean) => void; onLoaded: () => void; } const Flow = forwardRef((props: FlowProps, ref) => { const reactFlowWrapper = useRef(null); function getAllEdgeTypes() { return { buttonedge: ButtonEdge }; } const nodeTypes = useMemo(() => getAllNodeWithEaseOut(), []); const edgeTypes = useMemo(() => getAllEdgeTypes(), []); const [reactFlowInstance, setReactFlowInstance] = useState< ReactFlowInstance | undefined >(undefined); const [nodes, setNodes] = useState(props.nodes); const [edges, setEdges] = useState(props.edges); const [isPopupOpen, setIsPopupOpen] = useState(false); const [currentUserMessage, setCurrentUserMessage] = useState({ content: "", }); const [currentNodesRunning, setCurrentNodesRunning] = useState([]); const [errorCount, setErrorCount] = useState(0); const { getElement } = useVisibility(); const minimap = getElement("minimap"); useEffect(() => { const areNodesRunning = currentNodesRunning.length > 0; if (props.isRunning !== areNodesRunning) { props.onRunChange(areNodesRunning); } }, [currentNodesRunning]); const [{ isOver }, dropRef] = useDrop({ accept: "NODE", drop: (item, monitor) => { onDrop(item, monitor); }, collect: (monitor) => ({ isOver: monitor.isOver(), }), }); const onInit = (reactFlowInstance: ReactFlowInstance) => { setReactFlowInstance(reactFlowInstance); }; const addNode = (type: string, data?: any) => { const reactFlowBounds = ( reactFlowWrapper.current as any ).getBoundingClientRect(); const additionnalData = data?.additionnalData; const additionnalConfig = data?.additionnalConfig; if (typeof type === "undefined" || !type) { return; } const position = (reactFlowInstance as any).project({ x: reactFlowBounds.width / 2 - 100, y: reactFlowBounds.height / 2 - 100, }); const newNode = createNewNode( type, position, additionnalData, additionnalConfig, ); setNodes((nds) => nds.concat(newNode)); }; useImperativeHandle(ref, () => ({ addNode, })); useSocketListeners< FlowOnProgressEventData, FlowOnErrorEventData, FlowOnProgressEventData >(onProgress, onError, () => {}, onCurrentNodeRunning); function onProgress(data: FlowOnProgressEventData) { const nodeToUpdate = data.instanceName; const output = data.output; setCurrentNodesRunning((previous) => { return previous.filter((node) => node != nodeToUpdate); }); if (nodeToUpdate) { setNodes((prevNodes) => { return [ ...prevNodes.map((node: Node) => { if (node.data.name == nodeToUpdate) { node.data = { ...node.data, outputData: output, lastRun: new Date(), isDone: data.isDone, }; } return node; }), ]; }); } } function onError(data: FlowOnErrorEventData) { setCurrentNodesRunning((previous) => { return previous.filter((node) => node != data.instanceName); }); setCurrentUserMessage({ content: data.error, nodeId: data.instanceName ?? data.nodeName, type: MessageType.Error, }); setErrorCount((prevErrorCount) => prevErrorCount + 1); setIsPopupOpen(true); } function onCurrentNodeRunning(data: FlowOnCurrentNodeRunningEventData) { setCurrentNodesRunning((previous) => { return [...previous, data.instanceName]; }); } useEffect(() => { if (props.onFlowChange) { props.onFlowChange(nodes, edges, props.metadata); } }, [nodes, edges]); const onNodesChange: OnNodesChange = useCallback( (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), [setNodes], ); const onEdgesChange: OnEdgesChange = useCallback( (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), [setEdges], ); const onConnect: OnConnect = useCallback( (connection) => setEdges((eds) => { if ( isHandleAlreadyTargeted(connection, eds) || isSameNodeTargeted(connection) ) { return eds; } return addEdge( { ...connection, type: "buttonedge", markerEnd: "arrowClosed", }, eds, ); }), [setEdges], ); const onDragOver = useCallback((event: any) => { event.preventDefault(); if (!!event.dataTransfert) { event.dataTransfer.dropEffect = "move"; } }, []); const onDrop = useCallback( (item: any, monitor?: any) => { if ( !!reactFlowWrapper && !!reactFlowInstance && !!reactFlowWrapper.current ) { const reactFlowBounds = ( reactFlowWrapper.current as any ).getBoundingClientRect(); const type = item.nodeType; const additionnalData = item.additionnalData; const additionnalConfig = item.additionnalConfig; // check if the dropped element is valid if (typeof type === "undefined" || !type) { return; } const { x, y } = monitor.getClientOffset(); const position = (reactFlowInstance as any).project({ x: x - reactFlowBounds.left, y: y - reactFlowBounds.top, }); const newNode = createNewNode( type, position, additionnalData, additionnalConfig, ); setNodes((nds) => nds.concat(newNode)); } }, [reactFlowInstance], ); const isHandleAlreadyTargeted = (connection: Connection, eds: Edge[]) => { if ( eds.filter( (edge) => edge.targetHandle === connection.targetHandle && edge.target === connection.target, ).length > 0 ) { return true; } return false; }; const isSameNodeTargeted = (connection: Connection) => { if (connection.source === connection.target) { return true; } return false; }; const handlePopupClose = useCallback(() => { setIsPopupOpen(false); }, []); function handleChangeFlow(nodes: Node[], edges: Edge[]): void { setNodes(nodes); setEdges(edges); } const handleUpdateNodeData = (nodeId: string, data: any) => { const updatedNodes = nodes.map((node) => { if (node.id === nodeId) { return { ...node, data }; } return node; }); setNodes(updatedNodes); }; const handleUpdateNodes = (updatedNodes: Node[], updatesEdges: Edge[]) => { setNodes(updatedNodes); setEdges(updatesEdges); }; return (
{minimap.isVisible && }
); }); export default Flow; ================================================ FILE: packages/ui/src/components/LoadingScreen.tsx ================================================ import { LoadingScreenSpinner } from "./nodes/Node.styles"; const LoadingScreen = () => { return (
); }; export default LoadingScreen; ================================================ FILE: packages/ui/src/components/bars/Sidebar.tsx ================================================ import React, { useContext } from "react"; import { Edge, Node } from "reactflow"; import JSONView from "../side-views/JSONView"; import styled, { css } from "styled-components"; import { useTranslation } from "react-i18next"; import { useVisibility } from "../../providers/VisibilityProvider"; import CurrentNodeView from "../side-views/CurrentNodeView"; import ButtonRunAll from "../buttons/ButtonRunAll"; import { NodeContext } from "../../providers/NodeProvider"; import { Tabs, rem } from "@mantine/core"; import { FaFile } from "react-icons/fa"; import { MdCenterFocusStrong } from "react-icons/md"; import { FiChevronsLeft, FiChevronsRight } from "react-icons/fi"; interface SidebarProps { nodes: Node[]; edges: Edge[]; onChangeFlow: (nodes: Node[], edges: Edge[]) => void; } const Sidebar: React.FC = ({ nodes, edges, onChangeFlow }) => { const { t } = useTranslation("flow"); const { runAllNodes, currentNodesRunning } = useContext(NodeContext); const { getElement, sidepaneActiveTab, setSidepaneActiveTab } = useVisibility(); const sidebar = getElement("sidebar"); const show = sidebar.isVisible; const toggleShow = () => sidebar.toggle(); const iconStyle = { width: rem(12), height: rem(12) }; return ( <> {show ? : } {}} isRunning={currentNodesRunning?.length > 0} /> } onClick={() => setSidepaneActiveTab("json")} > {t("JsonView")} } onClick={() => setSidepaneActiveTab("current_node")} > {t("currentNodeView")} {!show &&
} ); }; const SidebarContainer = styled.div<{ show: boolean }>` position: fixed; right: 0; top: 0; bottom: 0; width: 30%; color: ${({ theme }) => theme.text}; background-color: ${({ theme }) => theme.bg}; box-shadow: -3px 0 3px rgba(0, 0, 0, 0.2); overflow-y: auto; transform: translateX(100%); transition: transform 0.2s ease-in-out; z-index: 9999; ${({ show }) => show && css` transform: translateX(0); `} `; const SidebarToggle = styled.div<{ show: boolean }>` position: fixed; right: 0; top: 50%; transform: translateY(-50%); width: 20px; height: 80px; background-color: #110a0e; border-top-left-radius: 10px; border-bottom-left-radius: 10px; transition: width 0.2s ease-in-out; z-index: 1; ${({ show }) => show && css` width: 31.5%; `} @media screen and (max-width: 768px) { display: none; } `; const ButtonsContainer = styled.div<{ show: boolean }>` position: fixed; right: 0; top: 3%; transform: translateY(-50%); transition: width 0.2s ease-in-out; z-index: 1000000; ${({ show }) => show && css` right: 31%; `} @media screen and (max-width: 768px) { display: none; } `; const ToggleIcon = styled.div` color: #a4a4a4d1; font-size: 1.5em; position: absolute; top: 50%; transform: translateY(-50%); :hover { color: #ffffff; } `; export default Sidebar; ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/DnDSidebar.tsx ================================================ import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { DnDNode, getSections, } from "../../../nodes-configuration/sectionConfig"; import { memo, useEffect, useState } from "react"; import DraggableNode from "./DraggableNode"; import { FiChevronDown, FiChevronLeft, FiChevronRight, FiSearch, } from "react-icons/fi"; import { useVisibility } from "../../../providers/VisibilityProvider"; import useIsTouchDevice from "../../../hooks/useIsTouchDevice"; import Section from "./Section"; import { DraggableNodeAdditionnalData } from "./types"; import { TextInput, Chip, Group } from "@mantine/core"; import { SubnodeData } from "../../../nodes-configuration/types"; import DraggableNodeWithSubnodes from "./DraggableNodeWithSubnodes"; const HIDE_SIDEBAR_ANIMATION_DURATION = 300; interface DnDSidebarProps { addNodeFromExt?: ( type: string, data: DraggableNodeAdditionnalData & Record, ) => void; } const DnDSidebar = ({ addNodeFromExt }: DnDSidebarProps) => { const { t } = useTranslation("flow"); const { getElement } = useVisibility(); const sidebar = getElement("dragAndDropSidebar"); const [contentVisible, setContentVisible] = useState(sidebar?.isVisible); const [sections, setSections] = useState(getSections()); const [searchQuery, setSearchQuery] = useState(""); const isTouchDevice = useIsTouchDevice(); // Update sidebar content visibility useEffect(() => { let timeoutId: NodeJS.Timeout; if (sidebar.isVisible) { setContentVisible(true); } else { timeoutId = setTimeout( () => setContentVisible(false), HIDE_SIDEBAR_ANIMATION_DURATION, ); } return () => clearTimeout(timeoutId); }, [sidebar]); // Update sections when hidden list changes useEffect(() => { const handleHiddenListChanged = (e: any) => { setSections(getSections()); }; window.addEventListener("nodesHiddenListChanged", handleHiddenListChanged); return () => { window.removeEventListener( "nodesHiddenListChanged", handleHiddenListChanged, ); }; }, []); function nodeMatchesSearch(node: DnDNode, query: string): boolean { if (!query) return true; const lowerQuery = query.toLowerCase(); return !!node.label.toLowerCase().includes(lowerQuery); } function subnodeMatchesSearch(subnode: SubnodeData, query: string): boolean { if (!query) return true; const lowerQuery = query.toLowerCase(); return !!subnode.label.toLowerCase().includes(lowerQuery); } function filterSubnodes( subnodes: SubnodeData[], searchQuery: string, ): SubnodeData[] { const subnodesMatches = subnodes.filter((subnode) => subnodeMatchesSearch(subnode, searchQuery), ); const isFilterEnabled = !!searchQuery; if (!isFilterEnabled && subnodesMatches.length > 7) { return subnodesMatches.slice(0, 8); } return subnodesMatches; } /** * Returns the node if it matches the search AND category filter, * OR if any of its subnodes match (in which case, only the matching subnodes are retained). */ function filterNode(node: DnDNode, searchQuery: string): DnDNode | null { const nodeMatchesOverall = nodeMatchesSearch(node, searchQuery); let filteredSubnodes: SubnodeData[] = []; if (node.subnodesShortcutConfig && node.subnodesShortcutConfig.length > 0) { filteredSubnodes = filterSubnodes( node.subnodesShortcutConfig, searchQuery, ); } if (nodeMatchesOverall || filteredSubnodes.length > 0) { return { ...node, // If some subnodes match, update them; otherwise, leave them unchanged. subnodesShortcutConfig: filteredSubnodes.length > 0 ? filteredSubnodes : node.subnodesShortcutConfig, }; } return null; } function renderNodeWithSubnode(nodeIndex: number, node: DnDNode) { const subNodeLabel = node.subnodesShortcutStyle?.title ? t(node.subnodesShortcutStyle?.title) : t("PopularModels"); return ( ); } const sectionsToRender = sections .map((section) => { if (!section.nodes) return null; const filteredNodes = section.nodes .map((node) => filterNode(node, searchQuery)) .filter((node): node is DnDNode => node !== null); return { ...section, nodes: filteredNodes }; }) .filter( (section) => section !== null && section.nodes !== undefined && section.nodes.length > 0, ); return (
{!sidebar.isVisible ? : }
{contentVisible && ( {/* Search bar */}
setSearchQuery(e.currentTarget.value)} leftSection={} size="xs" />
{/* Render sections (filtered by search query and category) */} {sectionsToRender.map((section, index) => { if (!section || !section.nodes || section.nodes.length === 0) { return null; } return (
{section.nodes?.map((node, nodeIndex) => { if (!node) return null; if ( node.subnodesShortcutConfig && node.subnodesShortcutConfig?.length > 0 ) { return renderNodeWithSubnode(nodeIndex, node); } return ( ); })}
); })}
)}
); }; const DnDSidebarContainer = styled.div``; export default memo(DnDSidebar); ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/DraggableNode.tsx ================================================ import { useDrag } from "react-dnd"; import { useTranslation } from "react-i18next"; import { DnDNode } from "../../../nodes-configuration/sectionConfig"; import { ReactNode, memo } from "react"; import styled from "styled-components"; import { toastCustomIconInfoMessage } from "../../../utils/toastUtils"; import { FiMenu, FiMove } from "react-icons/fi"; import { darken, lighten } from "polished"; import { Tooltip } from "@mantine/core"; import { GripIcon } from "./GripIcon"; import { DraggableNodeAdditionnalData } from "./types"; interface DraggableNodeProps extends DraggableNodeAdditionnalData { node: DnDNode; id?: string; } interface NodeBadgeProps { children?: ReactNode; color?: string; } const NodeBadge = ({ children, color = "#0369a1" }: NodeBadgeProps) => (
{children}
); const DraggableNode = (props: DraggableNodeProps) => { const { t } = useTranslation("flow"); const [{ isDragging }, drag] = useDrag({ type: "NODE", item: { nodeType: props.node.type, additionnalData: props.additionnalData, additionnalConfig: props.additionnalConfig, }, collect: (monitor) => { const result = { isDragging: monitor.isDragging(), }; return result; }, }); function showDragAndDropHelper() { if (localStorage.getItem("AIFLOW_didShowDragDropHelper") === "true") { return; } toastCustomIconInfoMessage( "Drag and drop nodes onto the canvas to add them.", FiMove, ); localStorage.setItem("AIFLOW_didShowDragDropHelper", "true"); } return ( { e.stopPropagation(); }} onTouchEnd={(e) => { e.stopPropagation(); }} onDoubleClick={(e) => { showDragAndDropHelper(); }} bandColor={props.node.color} className={`sidebar-dnd-node text-md text-af-text-element hover:ring-af-text-element/10 group group relative flex h-auto w-full cursor-grab flex-row items-center justify-between gap-x-1 overflow-hidden rounded-md py-2 text-center font-medium shadow-md transition-all duration-200 ease-in-out hover:ring-2 ${isDragging ? "opacity-10" : ""}`} >

{t(props.node.label)}

{props.node.isBeta && Beta} {props.node.isNew && New}
); }; export const Node = styled.div<{ bandColor?: string }>` background: linear-gradient( 120deg, ${({ bandColor }) => (bandColor ? lighten(0.05, bandColor) : "#84fab0")} 0%, ${({ bandColor }) => (bandColor ? darken(0.1, bandColor) : "#8fd3f4")} 100% ) left / 2% no-repeat, ${({ theme }) => theme.bg}; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; `; export default memo(DraggableNode); ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/DraggableNodeWithSubnodes.tsx ================================================ import React, { useState } from "react"; import { FiChevronDown, FiChevronRight, FiGrid } from "react-icons/fi"; import DraggableNode from "./DraggableNode"; import { DnDNode } from "../../../nodes-configuration/sectionConfig"; import { SubnodeData } from "../../../nodes-configuration/types"; import { useTranslation } from "react-i18next"; import { DraggableNodeAdditionnalData } from "./types"; interface DraggableNodeWithSubnodesProps { nodeIndex: number; node: DnDNode; subNodeLabel: string; subNodesData: SubnodeData[]; selectSubnodeComponent?: (props: { show: boolean; onClose: () => void; onValidate: (data?: any) => void; }) => JSX.Element; addNodeFromExt?: ( type: string, data: DraggableNodeAdditionnalData & Record, ) => void; } const DraggableNodeWithSubnodes: React.FC = ({ nodeIndex, node, subNodeLabel, subNodesData, selectSubnodeComponent, addNodeFromExt, }) => { const { t } = useTranslation("flow"); const [isExpanded, setIsExpanded] = useState(true); const [showMore, setShowMore] = useState(false); const toggleSubnodes = () => { setIsExpanded(!isExpanded); }; const subNodes = subNodesData.map( (subnodeData: SubnodeData, index: number) => { const subNode = { ...node }; subNode.label = subnodeData.label; subNode.isBeta = subnodeData.isBeta; subNode.isNew = subnodeData.isNew; subNode.helpMessage = subnodeData.description ?? node.helpMessage; return (
{index !== subNodesData.length - 1 && (
)}
); }, ); return (

{subNodeLabel}

{isExpanded ? ( ) : ( )}
{isExpanded && ( <>
{subNodes}
{/* Only show the "Show More" button if the component hasn't been displayed yet */} {!!selectSubnodeComponent && !showMore && ( )} {/* Render the component returned by selectSubnodeComponent when showMore is true */} {!!selectSubnodeComponent && showMore && (
{selectSubnodeComponent({ show: true, onClose: () => setShowMore(false), onValidate: (data?: any) => { if (!!data) { if (!!addNodeFromExt) { addNodeFromExt(node.type, { ...data, generateNow: true, }); } } setShowMore(false); }, })}
)} )}
); }; export default DraggableNodeWithSubnodes; ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/GripIcon.tsx ================================================ import { ComponentProps } from "react"; export function GripIcon(props: ComponentProps<"svg">) { return ( ); } ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/Section.tsx ================================================ import { useTranslation } from "react-i18next"; import { NodeSection } from "../../../nodes-configuration/sectionConfig"; import { FiArrowDown, FiChevronDown, FiChevronRight, FiChevronUp, } from "react-icons/fi"; import { useState } from "react"; interface SidebarSectionProps { section: NodeSection; index: number; children: React.ReactNode; } function SidebarSection({ section, index, children }: SidebarSectionProps) { const { t } = useTranslation("flow"); const [show, setShow] = useState(true); function toggleShow() { setShow((prev) => !prev); } return (

{section.icon && } {t(section.label)}

{show ? ( ) : ( )}
{show && children}
); } export default SidebarSection; ================================================ FILE: packages/ui/src/components/bars/dnd-sidebar/types.ts ================================================ export interface DraggableNodeAdditionnalData { additionnalData?: any; additionnalConfig?: any; } ================================================ FILE: packages/ui/src/components/buttons/ButtonRunAll.tsx ================================================ import styled, { keyframes } from "styled-components"; import { FaPlay, FaSpinner } from "react-icons/fa"; import { memo } from "react"; import TapScale from "../shared/motions/TapScale"; import { Tooltip } from "react-tooltip"; interface ButtonRunAllProps { onClick: () => void; isRunning: boolean; small?: boolean; } const ButtonRunAll: React.FC = ({ onClick, isRunning, small, }) => { return ( ); }; export default memo(ButtonRunAll); const spin = keyframes` 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } `; const Spinner = styled(FaSpinner)` animation: ${spin} 1s linear infinite; `; ================================================ FILE: packages/ui/src/components/buttons/ConfigurationButton.tsx ================================================ import React, { memo } from "react"; import { FiSettings } from "react-icons/fi"; import styled from "styled-components"; interface RightButtonProps { onClick: () => void; color?: string; icon?: React.ReactNode; text?: string; bottom?: string; } const RightIconButton: React.FC = ({ onClick, color = "#808080", icon = , bottom = "30px", }) => { return (
{icon}
); }; const StyledRightButton = styled.div<{ color: string; bottom: string }>` bottom: ${(props) => props.bottom}; background-color: ${(props) => props.color}; `; export default memo(RightIconButton); ================================================ FILE: packages/ui/src/components/edges/buttonEdge.tsx ================================================ import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, getStraightPath, useReactFlow, } from "reactflow"; export default function ButtonEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, markerEnd, data, }: EdgeProps) { const { setEdges } = useReactFlow(); const pathType = data?.pathType || "bezier"; let pathData = []; switch (pathType) { case "bezier": pathData = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, }); break; case "smoothstep": pathData = getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, }); break; case "step": pathData = getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, borderRadius: 0, }); break; case "straight": pathData = getStraightPath({ sourceX, sourceY, targetX, targetY, }); break; default: pathData = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, }); } const edgePath = pathData[0]; const labelX = pathData[1]; const labelY = pathData[2]; const onEdgeClick = () => { setEdges((edges) => edges.filter((edge) => edge.id !== id)); }; return ( <>
); } ================================================ FILE: packages/ui/src/components/handles/HandleWrapper.tsx ================================================ import ReactDOM from "react-dom"; import styled, { CSSProperties } from "styled-components"; import { InputHandle, OutputHandle } from "../nodes/Node.styles"; import { useMemo, useRef, useState } from "react"; import { Position } from "reactflow"; import React from "react"; export type LinkedHandlePositions = { [key in Position]: string[]; }; type HandleWrapperProps = { id: string; position: Position; linkedHandlePositions?: LinkedHandlePositions; isOutput?: boolean; onChangeHandlePosition: (newPosition: Position, id: string) => void; }; const HandleWrapper: React.FC = ({ id, position, onChangeHandlePosition, isOutput, linkedHandlePositions }) => { const HANDLE_DEFAULT_OFFSET = 20; const POPUP_DEFAULT_TOP_OFFSET = 10; const [showPopup, setShowPopup] = useState(false); const [currentPosition, setCurrentPosition] = useState(position) const [popupCoords, setPopupCoords] = useState<{ x: number; y: number } | null>(null); const ref = useRef(null); const closeTimeout = useRef(null); const openTimeout = useRef(null); const isHoveredRef = useRef(false); const handleMouseEnter = (event: React.MouseEvent) => { if (ref.current) { const rect = ref.current.getBoundingClientRect(); setPopupCoords({ x: rect.left + rect.width / 2, y: rect.top - POPUP_DEFAULT_TOP_OFFSET }); isHoveredRef.current = true; openTimeout.current = setTimeout(() => { if (!isHoveredRef.current) return; setShowPopup(true); }, 1000) } }; const cancelClose = () => { if (closeTimeout.current) { clearTimeout(closeTimeout.current); closeTimeout.current = null; } }; const startClose = () => { isHoveredRef.current = false; closeTimeout.current = setTimeout(() => { setShowPopup(false); }, 500); }; const changePosition = (newPosition: Position) => { setCurrentPosition(newPosition); onChangeHandlePosition(newPosition, id); } const adjustPositionByIndex = (): CSSProperties => { if (linkedHandlePositions == null) return {} const handleIndex = !!linkedHandlePositions[currentPosition] ? linkedHandlePositions[currentPosition].indexOf(id) : 0; switch (currentPosition) { case Position.Left: case Position.Right: return { marginTop: `${handleIndex * HANDLE_DEFAULT_OFFSET}px` }; case Position.Top: case Position.Bottom: return { marginLeft: `${handleIndex * HANDLE_DEFAULT_OFFSET}px` }; default: return {} } }; return ( <> {isOutput ? : } {showPopup && popupCoords && } ); }; type PopupProps = { currentPosition: Position; onSelect: (position: Position) => void; coords: { x: number; y: number }; onCancelClose: () => void; onStartClose: () => void; isOutput?: boolean; }; const Popup: React.FC = ({ currentPosition, onSelect, coords, onCancelClose, onStartClose, isOutput }) => { const handles = useMemo(() => [ { src: `./handle-left${isOutput ? '-out' : ''}.svg`, position: Position.Left }, { src: `./handle-right${isOutput ? '-out' : ''}.svg`, position: Position.Right }, { src: `./handle-top${isOutput ? '-out' : ''}.svg`, position: Position.Top }, { src: `./handle-bottom${isOutput ? '-out' : ''}.svg`, position: Position.Bottom }, ], [isOutput]); const popupContent = (
{handles.map((handle, index) => ( onSelect(handle.position)} alt="" /> ))}
); return ReactDOM.createPortal(popupContent, document.body); }; const StyledPopup = styled.div` transform: translate(-50%, -100%); `; export default HandleWrapper; ================================================ FILE: packages/ui/src/components/inputs/InputWithButton.tsx ================================================ import NodeTextField from "../nodes/node-input/NodeTextField"; interface InputWithButtonProps { buttonText: string; onInputChange: (value: string) => void; onButtonClick: () => void; value: string; inputPlaceholder?: string; inputClassName?: string; buttonClassName?: string; } const InputWithButton = ({ inputPlaceholder, buttonText, value, onInputChange, onButtonClick, inputClassName = "", buttonClassName = "", }: InputWithButtonProps) => { return (
onInputChange(event.target.value)} value={value} />
); }; export default InputWithButton; ================================================ FILE: packages/ui/src/components/nodes/AIDataSplitterNode.tsx ================================================ import React, { useContext, useEffect } from "react"; import { Position, NodeProps, useUpdateNodeInternals } from "reactflow"; import styled from "styled-components"; import { NodeContext } from "../../providers/NodeProvider"; import NodePlayButton from "./node-button/NodePlayButton"; import { generateIdForHandle } from "../../utils/flowUtils"; import { InputHandle, OutputHandle } from "./Node.styles"; import { useIsPlaying } from "../../hooks/useIsPlaying"; import { GenericNodeData } from "./types/node"; import SelectAutocomplete, { SelectItem, } from "../selectors/SelectAutocomplete"; import NodeTextField from "./node-input/NodeTextField"; import { Switch, Tooltip } from "@mantine/core"; import { useTranslation } from "react-i18next"; interface AIDataSplitterNodeData extends GenericNodeData { id: string; name: string; processorType: string; nbOutput: number; input: string; input_key: string; outputData?: string[]; lastRun: string; } interface AIDataSplitterNodeProps extends NodeProps { data: AIDataSplitterNodeData; } const separatorOptions: SelectItem[] = [ { value: ",", name: ",", }, { value: ";", name: ";", }, { value: "\\t", name: "\\t", }, { value: " ", name: "Space", }, { value: "\\n", name: "\\n", }, { value: "\\r", name: "\\r", }, ]; const AIDataSplitterNode: React.FC = React.memo( ({ data, id, selected }) => { const { t } = useTranslation("flow"); const updateNodeInternals = useUpdateNodeInternals(); const [isPlaying, setIsPlaying] = useIsPlaying(); const { onUpdateNodeData } = useContext(NodeContext); const modeOptions: SelectItem[] = [ { value: "ai", name: t("AI"), }, { value: "manual", name: t("Separator"), }, ]; useEffect(() => { const newNbOutput = data.outputData ? data.outputData.length : 0; if (!data.nbOutput || newNbOutput > data.nbOutput) { onUpdateNodeData(id, { ...data, nbOutput: newNbOutput, }); } setIsPlaying(false); }, [data.outputData]); useEffect(() => { updateNodeInternals(id); }, [data.nbOutput]); const handlePlayClick = () => { setIsPlaying(true); }; const handleForceNbOutputChange = ( event: React.ChangeEvent, ) => { const forcedNbOutput = Number(event.target.value); onUpdateNodeData(id, { ...data, nbOutput: forcedNbOutput, }); }; const handleChangeField = (field: string, value: any) => { onUpdateNodeData(id, { ...data, [field]: value, }); }; return (
{
e.stopPropagation()} onClick={(e) => e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()} >

mode

handleChangeField("mode", value)} /> {data["mode"] === "manual" && ( <>

{" "} custom_separator{" "}

handleChangeField("customSeparator", e.target.checked) } />

separator *

{!!data["customSeparator"] ? ( handleChangeField("separator", e.target.value) } /> ) : ( handleChangeField("separator", value) } /> )} )}

nb_output

}
{!!data.nbOutput && Array.from(Array(data.nbOutput)).map((_, index) => ( ))}
); }, ); const DataSplitterNodeContainer = styled.div<{ nbOutput: number; }>` min-height: ${(props) => props.nbOutput * 30 + 100}px; width: 200px; transition: all 0.3s ease-in-out; `; const ForceNbOutputInput = styled.input` font-size: 0.9em; color: ${({ theme }) => theme.text}; padding: 5px; border-radius: 5px; `; export default AIDataSplitterNode; ================================================ FILE: packages/ui/src/components/nodes/DisplayNode.tsx ================================================ import React, { useContext, useEffect, useMemo, useState } from "react"; import { Position, NodeProps, useUpdateNodeInternals, ResizeParams, NodeResizeControl, } from "reactflow"; import { generateIdForHandle } from "../../utils/flowUtils"; import { NodeContext } from "../../providers/NodeProvider"; import { useIsPlaying } from "../../hooks/useIsPlaying"; import NodePlayButton from "./node-button/NodePlayButton"; import HandleWrapper from "../handles/HandleWrapper"; import useHandlePositions from "../../hooks/useHandlePositions"; import { GenericNodeData } from "./types/node"; import { NodeBand, NodeHeader, NodeIcon, NodeTitle } from "./Node.styles"; import OutputDisplay from "./node-output/OutputDisplay"; import { useTranslation } from "react-i18next"; import { FaTv } from "react-icons/fa"; interface DisplayNodeData extends GenericNodeData { handles: any; id: string; name: string; processorType: string; nbOutput: number; input: string; input_key: string; outputData?: string[]; lastRun: string; } interface DisplayNodeProps extends NodeProps { data: DisplayNodeData; } interface Dimensions { width: number; height: number; } function ResizeIcon() { return ( ); } const DisplayNode: React.FC = React.memo( ({ data, id, selected }) => { const { t } = useTranslation("flow"); const { onUpdateNodeData } = useContext(NodeContext); const [nodeId, setNodeId] = useState(`${data.name}-${Date.now()}`); const [dimensions, setDimensions] = useState({ width: data.nodeDimensions?.width ?? 450, height: data.nodeDimensions?.height ?? 200, }); const [reloadDisplay, setReloadDisplay] = useState(0); const [isPlaying, setIsPlaying] = useIsPlaying(); const updateNodeInternals = useUpdateNodeInternals(); const inputHandleId = useMemo(() => generateIdForHandle(0), []); const outputHandleId = useMemo(() => generateIdForHandle(0, true), []); const { allHandlePositions } = useHandlePositions(data, 1, [ outputHandleId, ]); useEffect(() => { setNodeId(`${data.name}-${Date.now()}`); setIsPlaying(false); updateNodeInternals(id); }, [data.lastRun]); const handlePlayClick = () => { setIsPlaying(true); }; const handleChangeHandlePosition = ( newPosition: Position, handleId: string, ) => { onUpdateNodeData(id, { ...data, handles: { ...data.handles, [handleId]: newPosition, }, }); updateNodeInternals(id); }; const handleReloadDisplay = () => { setReloadDisplay(reloadDisplay + 1); }; const handleSaveDimensions = (params: ResizeParams) => { setDimensions({ width: params.width, height: params.height, }); }; return (
{selected && ( { handleReloadDisplay(); handleSaveDimensions(params); }} style={{ backgroundColor: "transparent", border: "none", }} > )} {data.appearance?.customName ?? t("Display")}
{data.outputData != null ? ( ) : (
)}
); }, ); export default DisplayNode; ================================================ FILE: packages/ui/src/components/nodes/FileUploadNode.tsx ================================================ import { useContext, useEffect, useState } from "react"; import { FaFileAlt, FaLink } from "react-icons/fa"; import { NodeProps, Position, useUpdateNodeInternals } from "reactflow"; import HandleWrapper from "../handles/HandleWrapper"; import { generateIdForHandle } from "../../utils/flowUtils"; import { NodeContext } from "../../providers/NodeProvider"; import { useIsPlaying } from "../../hooks/useIsPlaying"; import NodePlayButton from "./node-button/NodePlayButton"; import { LoadingSpinner, NodeBand, NodeContainer, NodeHeader, NodeIcon, NodeTitle, } from "./Node.styles"; import { useTranslation } from "react-i18next"; import { toastErrorMessage } from "../../utils/toastUtils"; import { GenericNodeData } from "./types/node"; import { getOutputExtension } from "./node-output/outputUtils"; import NodeOutput from "./node-output/NodeOutput"; import OptionSelector, { Option } from "../selectors/OptionSelector"; import InputWithButton from "../inputs/InputWithButton"; import FileDropZone from "../selectors/FileDropZone"; import { getUploadAndDownloadUrl, uploadWithS3Link, } from "../../api/uploadFile"; import { useLoading } from "../../hooks/useLoading"; import HintComponent from "./utils/HintComponent"; interface GenericNodeProps extends NodeProps { data: GenericNodeData; id: string; selected: boolean; } type FileChoice = "url" | "upload"; const fileChoices: Option[] = [ { name: "URL", icon: , value: "url", }, { name: "Upload", icon: , value: "upload", }, ]; const accept = { "video/mp4": [".mp4"], "audio/mpeg": [".mp3"], "image/png": [".png"], "image/jpeg": [".jpg", ".jpeg"], "image/gif": [".gif"], "text/plain": [".txt"], "application/pdf": [".pdf"], "model/gltf-binary": [".glb"], "model/gltf+json": [".gltf"], "model/gltf": [".gltf"], "model/obj": [".obj"], }; const FileUploadNode = ({ data, id }: GenericNodeProps) => { const { onUpdateNodeData } = useContext(NodeContext); const { t } = useTranslation("flow"); const [files, setFiles] = useState(null); const updateNodeInternals = useUpdateNodeInternals(); const [isPlaying, setIsPlaying] = useIsPlaying(); const [collapsed, setCollapsed] = useState( data.outputData ? true : false, ); const [showLogs, setShowLogs] = useState( data.outputData ? true : false, ); const [url, setUrl] = useState(""); const [fileChoiceSelected, setFileChoiceSelected] = useState(data?.fileChoiceSelected); const [isLoading, startLoadingWith] = useLoading(); useEffect(() => { if (data.isDone) { setIsPlaying(false); } updateNodeInternals(id); }, [data.lastRun, data.outputData]); async function uploadFile(files: File[]) { const filename = files[0].name; const urls = await getUploadAndDownloadUrl(filename); const uploadData = urls.upload_data; await uploadWithS3Link(uploadData, files[0]); return urls; } async function processFiles(files: File[]) { if (!files || files.length === 0) return; let urls: any; let uploadError: boolean = false; try { urls = await startLoadingWith(uploadFile, files); } catch (error) { toastErrorMessage(t("error.upload_failed")); uploadError = true; setFiles(null); } finally { if (uploadError) return; } const outputType = getOutputExtension(files[0].name); onUpdateNodeData(id, { ...data, fileUrl: urls.download_link, outputData: urls.download_link, lastRun: new Date(), config: { ...data.config, outputType, }, }); setShowLogs(true); setCollapsed(true); } const handleAcceptFiles = (files: File[]) => { if (files) { setFiles(files); processFiles(files); } }; const handleChangeHandlePosition = ( newPosition: Position, handleId: string, ) => { onUpdateNodeData(id, { ...data, handles: { ...data.handles, [handleId]: newPosition, }, }); updateNodeInternals(id); }; const handlePlayClick = () => { setIsPlaying(true); }; const toggleCollapsed = () => { setCollapsed(!collapsed); }; const handleSetFileViaURL = () => { if (!url) return; const outputType = getOutputExtension(url); onUpdateNodeData(id, { ...data, fileUrl: url, outputData: url, lastRun: new Date(), config: { ...data.config, outputType, }, }); setShowLogs(true); setCollapsed(true); }; function handleFileChoiceSelected(choice: FileChoice | null) { setFileChoiceSelected(choice); onUpdateNodeData(id, { ...data, fileChoiceSelected: choice, }); } const hideFields = isLoading || collapsed; return ( {data.appearance?.customName ?? t("File")} {!hideFields && (
handleFileChoiceSelected(option.value)} options={fileChoices} selectedOption={fileChoiceSelected} />
)} {isLoading && (
)} {!hideFields && fileChoiceSelected === "upload" && (
)} {!hideFields && fileChoiceSelected === "url" && (
)} setShowLogs(!showLogs)} data={data} />
); }; export default FileUploadNode; ================================================ FILE: packages/ui/src/components/nodes/GenericNode.tsx ================================================ import React, { useState, useEffect, useContext, useMemo, FC } from "react"; import { Position, NodeProps, useUpdateNodeInternals } from "reactflow"; import { NodeContainer, NodeHeader, NodeIcon, NodeTitle, NodeContent, NodeForm, NodeBand, } from "./Node.styles"; import useHandleShowOutput from "../../hooks/useHandleShowOutput"; import { generateIdForHandles, getKeyFromHandleName, getTargetHandleKey, } from "../../utils/flowUtils"; import { getIconComponent } from "./utils/NodeIcons"; import { Field, NodeConfig, NodeSubConfig, } from "../../nodes-configuration/types"; import { NodeContext } from "../../providers/NodeProvider"; import NodePlayButton from "./node-button/NodePlayButton"; import { useTranslation } from "react-i18next"; import { useIsPlaying } from "../../hooks/useIsPlaying"; import { GenericNodeData, NodeData } from "./types/node"; import HandleWrapper from "../handles/HandleWrapper"; import useHandlePositions from "../../hooks/useHandlePositions"; import { useFormFields } from "../../hooks/useFormFields"; import NodeOutput from "./node-output/NodeOutput"; import { getDynamicConfig } from "../../api/nodes"; import { getAdequateConfigFromDiscriminators, getDefaultOptions, getNbInputs, getNbOutputs, hasDiscriminatorChanged, } from "../../utils/nodeConfigurationUtils"; import { evaluateCondition } from "../../utils/evaluateConditions"; interface GenericNodeProps extends NodeProps { data: GenericNodeData; id: string; selected: boolean; nodeFields?: Field[]; iconComponent?: FC; } const GenericNode: React.FC = React.memo( ({ data, id, selected, nodeFields, iconComponent }) => { const { t } = useTranslation("flow"); const { hasParent, showOnlyOutput, onUpdateNodeData, getIncomingEdges, overrideConfigForNode, findNode, removeEdgesByIds, } = useContext(NodeContext); const updateNodeInternals = useUpdateNodeInternals(); const nbOutput = getNbOutputs(data); const [collapsed, setCollapsed] = useState(false); const [showLogs, setShowLogs] = useState( data.config?.defaultHideOutput == null ? true : !data.config.defaultHideOutput, ); const [fields, setFields] = useState( !!data.config?.fields ? data.config.fields : !!nodeFields ? nodeFields : [], ); useEffect(() => { if (data.isDone) setIsPlaying(false); if (!data.config.defaultHideOutput) { setShowLogs(true); } else { setShowLogs(false); } }, [data.lastRun, data.outputData]); useEffect(() => { if (!data.config?.fields?.some((field) => field.hasHandle)) return; const fieldsToNullify: any = {}; const edgesKeys = getIncomingEdges(id)?.map((edge) => getTargetHandleKey(edge), ); const fieldsWithValidCondition = fields.filter((field) => { if (field?.condition) { const condition = field.condition; return evaluateCondition(condition, data); } return true; }); edgesKeys?.forEach((key) => { fieldsToNullify[fieldsWithValidCondition[key]?.name] = undefined; }); const fieldsUpdated = fields.map((field) => { if (field.name in fieldsToNullify) { field.isLinked = true; } else { field.isLinked = false; } return field; }); const currentNodeData = findNode(id)?.data; if (!!currentNodeData) { onUpdateNodeData(id, { ...currentNodeData, ...fieldsToNullify, config: { ...currentNodeData.config, fields: fieldsUpdated, inputNames: fieldsWithValidCondition.map((field) => field.name), }, }); } }, [getIncomingEdges(id)?.length]); const outputHandleIds = useMemo( () => generateIdForHandles(nbOutput, true), [nbOutput], ); const nbInput = useMemo( () => getNbInputs(data, fields), [data?.config?.inputNames], ); useEffect(() => { const incomingEdges = getIncomingEdges(id) || []; const incomingEdgeKeys = incomingEdges.map((edge) => getKeyFromHandleName(edge.targetHandle ?? ""), ); const keysToRemove = incomingEdgeKeys.filter((key) => +key >= nbInput); const edgesIdToRemove = incomingEdges .filter((edge) => keysToRemove.includes(getKeyFromHandleName(edge.targetHandle ?? "")), ) .map((edge) => edge.id); if (edgesIdToRemove.length) removeEdgesByIds(edgesIdToRemove); }, [data?.config?.inputNames]); const [isPlaying, setIsPlaying] = useIsPlaying(); useHandleShowOutput({ showOnlyOutput, setCollapsed: setCollapsed, setShowLogs: setShowLogs, }); const formFields = useFormFields( data, id, handleNodeFieldChange, setDefaultOptions, hasParent, { showHandles: data.config.showHandlesNames, showLabels: data.config.showHandlesNames, showOnlyConnectedFields: collapsed, }, handleNodeDataChange, ); const { allInputHandleIds, allHandlePositions } = useHandlePositions( data, nbInput, outputHandleIds, ); const toggleCollapsed = () => { setCollapsed(!collapsed); }; const handlePlayClick = () => { setIsPlaying(true); }; function handleNodeDataChange(data: GenericNodeData) { onUpdateNodeData(id, data); updateNodeInternals(id); if (data.config.fields) { setFields(data.config.fields); } } function handleNodeFieldChange( fieldName: string, value: any, target?: any, ) { const selectionStart = target?.selectionStart; const selectionEnd = target?.selectionEnd; const newNodeData = { ...data, [fieldName]: value, }; onUpdateNodeData(id, newNodeData); if (hasDiscriminatorChanged(fieldName, newNodeData)) { updateConfigWithDiscriminator(newNodeData); } if (!!target) { requestAnimationFrame(() => { target.selectionStart = selectionStart; target.selectionEnd = selectionEnd; }); } if (fieldName === "config") { updateNodeInternals(id); } } function updateConfigWithDiscriminator(nodeData: NodeData) { const newConfig = getAdequateConfigFromDiscriminators(nodeData)?.config; if (!newConfig) return; if (!!newConfig) { overrideConfigForNode(id, newConfig, nodeData); setFields(newConfig.fields); } } function setDefaultOptions() { const defaultOptions: any = getDefaultOptions(data.config.fields, data); onUpdateNodeData(id, { ...data, ...defaultOptions, }); } function handleChangeHandlePosition( newPosition: Position, handleId: string, ) { onUpdateNodeData(id, { ...data, handles: { ...data.handles, [handleId]: newPosition, }, }); updateNodeInternals(id); } function updateConfig(config: NodeConfig) { const defaultOptions: any = getDefaultOptions(config.fields, data); onUpdateNodeData(id, { ...data, ...defaultOptions, config: { ...config, isDynamicallyGenerated: false, }, }); setFields(config.fields); } function updateConfigVariant(variantConf: NodeSubConfig) { const defaultConfigEnabled = variantConf.subConfigurations[0].config; const discriminators = variantConf.subConfigurations[0].discriminators; const defaultFields = defaultConfigEnabled.fields; const defaultOptions: any = getDefaultOptions(defaultFields, data); onUpdateNodeData(id, { ...data, ...defaultOptions, ...discriminators, config: { ...defaultConfigEnabled, isDynamicallyGenerated: false, }, variantConfig: { ...variantConf, }, }); setFields(defaultFields); } async function handleGetDynamicConfig() { if (data.config.processorType == null) return; const newConfig = await getDynamicConfig(data.config.processorType, data); if (newConfig.subConfigurations != null) { updateConfigVariant(newConfig); } else { updateConfig(newConfig); } } const NodeIconComponent = !!iconComponent ? iconComponent : getIconComponent(data.config.icon); const displayInputs = data.config.hasInputHandle && !data.config.showHandlesNames; const hideNodeParams = (hasParent(id) && data.config.hideFieldsIfParent) || collapsed; return ( {displayInputs && ( <> {allInputHandleIds.map((id) => { return ( ); })} )} {NodeIconComponent && } {data.appearance?.customName ?? t(data.config.nodeName)} {outputHandleIds.map((id, index) => { return ( ); })} {(!hideNodeParams || data.config.showHandlesNames) && ( {formFields} {data.config.isDynamicallyGenerated && ( )} )} setShowLogs(!showLogs)} data={data} /> ); }, propsAreEqual, ); function propsAreEqual( prevProps: GenericNodeProps, nextProps: GenericNodeProps, ) { if ( prevProps.selected !== nextProps.selected || prevProps.id !== nextProps.id ) { return false; } for (let key in prevProps.data) { if ( key !== "x" && key !== "y" && prevProps.data[key] !== nextProps.data[key] ) { return false; } } for (let key in nextProps.data) { if ( key !== "x" && key !== "y" && nextProps.data[key] !== prevProps.data[key] ) { return false; } } return true; } export default GenericNode; ================================================ FILE: packages/ui/src/components/nodes/Node.styles.ts ================================================ import styled, { css, keyframes } from "styled-components"; import ReactFlow, { MiniMap, Controls, Panel, Handle } from "reactflow"; import { createGlobalStyle } from "styled-components"; import { darken } from "polished"; import { FiCopy } from "react-icons/fi"; import { FaSpinner } from "react-icons/fa"; export const GlobalStyle = createGlobalStyle` body { font-family: 'Roboto', sans-serif; } `; export const NodeHeader = styled.div` display: flex; align-items: center; justify-content: space-between; font-size: 1.4em; min-height: 70px; background-color: ${({ theme }) => theme.nodeBg}; padding-top: 0.1em; padding-bottom: 0.1em; padding-left: 1em; padding-right: 1em; border-top-left-radius: 8px; border-top-right-radius: 8px; cursor: pointer; color: ${({ theme }) => theme.text}; transition: all 0.3s ease; `; export const NodeBand = styled.div<{ selected?: boolean; color?: string }>` padding: 2px; overflow: hidden; transition: height 0.2s ease-out background 0.3s ease; background: ${({ theme, selected, color }) => color ? color : selected ? theme.accentSelected : theme.accent}; `; export const NodeTitle = styled.div` font-weight: 600; color: ${({ theme }) => theme.text}; `; export const NodeContent = styled.div.attrs({ className: "flex flex-col h-auto w-full flex-grow justify-center p-4", })` color: ${({ theme }) => theme.text}; `; export const NodeForm = styled.div` display: flex; height: 100%; width: 100%; flex-direction: column; gap: 8px; `; export const NodeLabel = styled.label``; export const StyledNodeTextarea = styled.textarea<{ withMinHeight?: boolean }>` padding: 12px 24px; border: none; border-radius: 8px; font-size: 1.1em; line-height: 1.5em; background-color: ${({ theme }) => theme.nodeInputBg}; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); color: ${({ theme }) => theme.text}; resize: vertical; min-height: ${({ withMinHeight }) => (withMinHeight ? "8rem" : undefined)}; width: 100%; height: auto; transition: box-shadow 0.3s ease-in-out, background-color 0.3s ease; &:hover, &:focus { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } `; export const NodeIcon = styled.div` display: flex; justify-content: center; align-items: center; height: 100%; color: ${({ theme }) => theme.text}; max-width: 1.3em; font-size: 1.3em; `; export const NodeContainer = styled.div<{ width?: number }>` width: 35em; background: ${({ theme }) => theme.nodeGradientBg}; background-color: ${({ theme }) => theme.bg}; box-shadow: ${({ theme }) => theme.boxShadow}; border-radius: 8px; transition: all 0.3s ease; `; export const NodeLogsText = styled.p` font-size: 1em; margin: 0; color: ${({ theme }) => theme.text}; `; export const NodeLogs = styled.div<{ showLogs: boolean; noPadding?: boolean }>` border-radius: 0 0 8px 8px; font-size: 1.1em; line-height: 1.4em; padding: ${({ noPadding }) => (noPadding ? "0px" : "10px 16px")}; overflow: hidden; word-break: break-word; transition: height 0.2s ease-out background 0.3s ease; background: ${({ theme }) => theme.outputBg}; color: ${({ theme }) => theme.accentText}; cursor: pointer; max-height: 700px; overflow-y: auto; overflow-wrap: break-word; `; export const OptionSelector = styled.div` display: flex; flex-direction: row; justify-content: space-around; align-items: center; width: 100%; height: fit-content; border: 2px solid ${({ theme }) => theme.accent}; border-radius: 4px; overflow: hidden; background-color: ${({ theme }) => theme.bg}; box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.1); padding: 3px; gap: 5px; `; export const OptionButton = styled.button<{ selected: boolean }>` flex-grow: 1; padding: 10px 10px; font-size: 1.1rem; background: ${({ selected, theme }) => selected ? theme.optionButtonBgSelected : null}; color: ${({ selected, theme }) => selected ? theme.optionButtonColorSelected : theme.optionButtonColor}; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; text-align: center; font-weight: bold; &:hover { background-color: ${({ selected, theme }) => selected ? theme.optionButtonBg : darken(0.1, theme.optionButtonBg)}; color: ${({ theme }) => theme.optionButtonColorSelected}; } `; export const NodeSelect = styled.select` padding: 10px 16px; border: none; border-radius: 5px; font-size: 1.1em; background-color: ${({ theme }) => theme.nodeInputBg}; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); color: ${({ theme }) => theme.text}; resize: vertical; height: fit-content; transition: all 0.3s ease; `; export const NodeSelectOption = styled.option` padding: 10px 16px; `; export const ReactFlowStyled = styled(ReactFlow)` .react-flow__attribution { background: transparent; } `; export const MiniMapStyled = styled(MiniMap)` background-color: ${(props) => props.theme.minimapBg}; .react-flow__minimap-mask { fill: ${(props) => props.theme.minimapMaskBg}; } .react-flow__minimap-node { fill: ${(props) => props.theme.minimapMaskBg}; stroke: none; } @media screen and (max-width: 768px) { display: none; } `; export const ControlsStyled = styled(Controls)` button { background-color: ${(props) => props.theme.controlsBg}; color: ${(props) => props.theme.controlsColor}; border-bottom: 1px solid ${(props) => props.theme.controlsBorder}; &:hover { background-color: ${(props) => props.theme.controlsBgHover}; } path { fill: currentColor; } } `; export const CopyButton = styled.button` background-color: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; `; export const CopyIcon = styled(FiCopy)` color: ${(props) => props.theme.controlsColor}; :hover { color: #000000; } `; export const InputHandle = styled(Handle)<{ required?: boolean }>` z-index: 45; background: ${({ required }) => (required ? "#F09686" : "#72c8fa")}; width: 0.75em; height: 0.75em; @media (max-width: 600px) { width: 1.25em; height: 1.25em; } border-radius: 50%; border: none; box-shadow: ${({ required }) => required ? "0 0 10px 2px rgba(240, 150, 134, 0.5)" : "0 0 10px 2px rgba(114, 200, 250, 0.3)"}; transition: background 0.3s ease, box-shadow 0.3s ease; &:hover { background: #89d0fc; box-shadow: 0 0 15px 7px rgba(114, 200, 250, 0.5); } `; export const OutputHandle = styled(Handle)` z-index: 45; background: rgb(224, 166, 79); width: 10px; height: 10px; box-shadow: 0 0 10px 2px rgba(224, 166, 79, 0.3); border-radius: 0; border: none; transition: background 0.3s ease, box-shadow 0.3s ease; @media (max-width: 600px) { width: 1.25em; height: 1.25em; } &:hover { background: rgb(234, 176, 89); box-shadow: 0 0 15px 7px rgba(224, 166, 79, 0.5); } `; const spin = keyframes` 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } `; export const LoadingIcon = styled(FaSpinner)` animation: ${spin} 1s linear infinite; `; export const LoadingSpinner = styled(FaSpinner)` animation: ${spin} 1s linear infinite; `; export const LoadingScreenSpinner = styled.div` border: 4px solid rgba(0, 0, 0, 0.1); border-radius: 50%; border-left-color: rgb(132, 250, 176); animation: ${spin} 1s ease infinite; `; ================================================ FILE: packages/ui/src/components/nodes/NodeHelpPopover.tsx ================================================ import React from "react"; import { Popover } from "@mantine/core"; import { NodeHelp, NodeHelpData } from "./utils/NodeHelp"; type NodeHelpPopoverProps = { children: React.ReactNode; showHelp: boolean; data: NodeHelpData; onClose: () => void; }; function NodeHelpPopover({ children, showHelp, data, onClose, }: NodeHelpPopoverProps) { return ( {children} {data && } ); } export default NodeHelpPopover; ================================================ FILE: packages/ui/src/components/nodes/NodeWrapper.tsx ================================================ import React, { useContext, useState } from "react"; import { NodeContext } from "../../providers/NodeProvider"; import { FaCopy, FaEraser, FaQuestionCircle, FaRegCopy } from "react-icons/fa"; import ActionGroup, { Action } from "../selectors/ActionGroup"; import { MdDelete, MdEdit, MdMenuOpen } from "react-icons/md"; import { useVisibility } from "../../providers/VisibilityProvider"; import { useTranslation } from "react-i18next"; import ColorSelector from "../selectors/ColorSelector"; import { NodeHelpData } from "./utils/NodeHelp"; import NodeHelpPopover from "./NodeHelpPopover"; type NodeWrapperProps = { children: React.ReactNode; nodeId: string; }; type NodeActions = | "clear" | "duplicate" | "ref" | "remove" | "sidepane" | "color" | "name" | "helper"; function NodeWrapper({ children, nodeId }: NodeWrapperProps) { const { t } = useTranslation("flow"); const { t: tHelp } = useTranslation("nodeHelp"); const { getElement, setSidepaneActiveTab } = useVisibility(); const { findNode, duplicateNode, removeNode, clearNodeOutput, setCurrentNodeIdSelected, updateNodeAppearance, } = useContext(NodeContext); const currentNode = findNode(nodeId); const currentNodeHelp = tHelp(currentNode?.data.processorType, { returnObjects: true, }) as NodeHelpData; const currentNodeColor = currentNode?.data?.appearance?.color; const currentNodeName = currentNode?.data?.appearance?.customName ?? t(currentNode?.data?.config?.nodeName); const currentNodeIsMissingFields = currentNode?.data?.missingFields?.length > 0; const [showActions, setShowActions] = useState(false); const [showColors, setShowColors] = useState(false); const [showTextField, setShowTextField] = useState(false); const [showHelp, setShowHelp] = useState(false); let hideActionsTimeout: ReturnType; const hideActionsWithDelay = () => { hideActionsTimeout = setTimeout(() => { setShowColors(false); setShowTextField(false); setShowActions(false); }, 2000); }; const clearHideActionsTimeout = () => { if (hideActionsTimeout) { clearTimeout(hideActionsTimeout); } }; function handleOpenSidepane(): void { getElement("sidebar").show(); setSidepaneActiveTab("current_node"); } function handleChangeNodeColor(color: string): void { if (color === "transparent") { updateNodeAppearance(nodeId, { color: undefined }); } else { updateNodeAppearance(nodeId, { color }); } } function toggleHelp(): void { setShowHelp(!showHelp); } const actions: Action[] = [ { icon: (
), name: t("NodeColor"), value: "color", tooltipPosition: "left", onClick: () => { setShowColors(!showColors); setShowTextField(false); }, }, { icon: , name: t("ChangeName"), value: "name", onClick: () => { setShowTextField(!showTextField); setShowColors(false); }, }, { icon: , name: t("Duplicate"), value: "duplicate", onClick: () => duplicateNode(nodeId), }, // { // icon: , // name: t("CreateRef"), // value: "ref", // onClick: () => createNodeRef(nodeId), // }, { icon: , name: t("OpeninSidepane"), value: "sidepane", onClick: () => handleOpenSidepane(), }, { icon: , name: t("ClearOutput"), value: "clear", onClick: () => clearNodeOutput(nodeId), }, { icon: , name: t("Help"), value: "helper", onClick: () => toggleHelp(), }, { icon: , name: t("RemoveNode"), value: "remove", onClick: () => { setCurrentNodeIdSelected(""); removeNode(nodeId); }, hoverColor: "text-red-400", }, ]; return ( setShowHelp(false)} >
{ setShowActions(true); setCurrentNodeIdSelected(nodeId); }} onMouseLeave={() => { hideActionsWithDelay(); }} onMouseEnter={clearHideActionsTimeout} > {children}

{t("EnterCustomName")}

updateNodeAppearance(nodeId, { customName: e.target.value }) } onKeyDown={(e) => { if (e.key === "Enter") { setShowTextField(false); } }} />
); } export default NodeWrapper; ================================================ FILE: packages/ui/src/components/nodes/ReplicateNode.tsx ================================================ import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { NodeProps } from "reactflow"; import { Field } from "../../nodes-configuration/types"; import { LoadingSpinner, NodeContainer } from "./Node.styles"; import { NodeData } from "./types/node"; import InputWithButton from "../inputs/InputWithButton"; import { getModelConfig } from "../../api/replicateModels"; import withCache from "../../api/cache/withCache"; import { toastErrorMessage } from "../../utils/toastUtils"; import GenericNode from "./GenericNode"; import { NodeContext } from "../../providers/NodeProvider"; import { getIconComponent } from "./utils/NodeIcons"; import SelectModelPopup from "../popups/select-model-popup/SelectModelPopup"; import { getSchemaFromConfig, convertOpenAPISchemaToNodeConfig, } from "../../utils/openAPIUtils"; interface ReplicateNodeData extends NodeData { schema: any; } interface DynamicFieldsProps extends NodeProps { data: ReplicateNodeData; } export default function ReplicateNode({ data, id, selected, isConnectable, type, xPos, yPos, zIndex, }: DynamicFieldsProps) { const { t } = useTranslation("flow"); const [modelInput, setModelInput] = useState(""); const modelRef = useRef( !!data.config?.nodeName ? data.config.nodeName : undefined, ); const fieldsRef = useRef( !!data.config?.fields ? data.config.fields : [], ); const [showPopup, setShowPopup] = useState(false); const { onUpdateNodeData, findNode } = useContext(NodeContext); function formatName(name: string) { return name .replace(/([A-Z])/g, " $1") .replace(/[_\-]+/g, " ") .trim() .split(" ") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } function arrangeOldConfig() { onUpdateNodeData(id, { ...data, nodeLoaded: true, model: data.config.nodeName, config: { ...data.config, showHandlesNames: true, nodeName: data.config?.nodeName.split(":")[0], }, }); } useEffect(() => { if ( !!data?.config?.fields && !!data?.config?.nodeName && !data.nodeLoaded ) { arrangeOldConfig(); } }); useEffect(() => { async function configureNode() { if (!modelRef.current) return; let response; let fields: Field[] = []; try { response = await withCache( getModelConfig, modelRef.current, data.processorType, ); const inputSchema = getSchemaFromConfig(response, "Input"); fields = convertOpenAPISchemaToNodeConfig(inputSchema, response); } catch (error) { toastErrorMessage( `Error fetching configuration for following model : "${modelRef.current}". \n\n Here's a valid model name as an example : fofr/become-image `, ); } if (!response) return; const modelId = response.modelId; modelRef.current = modelRef.current + ":" + modelId; fieldsRef.current = fields; let modelNameToDisplay = getModelNameToDisplay(); const newFieldData: any = getNewFieldData(fieldsRef.current); onUpdateNodeData(id, { ...data, ...newFieldData, model: modelRef.current, config: { ...data.config, fields: fieldsRef.current, inputNames: fields.map((field) => field.name), showHandlesNames: true, nodeName: modelNameToDisplay, }, nodeLoaded: true, }); } if (fieldsRef.current.length > 0 || !modelRef.current) return; configureNode(); }, [modelRef.current]); useEffect(() => { if (!fieldsRef.current || fieldsRef.current.length === 0) return; const newFieldData: any = getNewFieldData(fieldsRef.current); const currentNodeData = findNode(id)?.data; onUpdateNodeData(id, { ...currentNodeData, ...newFieldData, config: { ...currentNodeData.config, inputNames: fieldsRef.current.map((field) => field.name), fields: fieldsRef.current, }, }); }, [fieldsRef.current]); function getModelNameToDisplay() { let modelNameToDisplay = modelRef.current?.includes(":") ? modelRef.current.split(":")[0] : modelRef.current; if (!modelNameToDisplay) return; modelNameToDisplay = modelNameToDisplay.includes("/") ? formatName(modelNameToDisplay.split("/")[1]) : modelNameToDisplay; return modelNameToDisplay; } function getNewFieldData(fields: Field[]) { const newFieldData: any = {}; fields.forEach((field) => { if (field.defaultValue != null) { if (data[field.name] == null && !field.isLinked) { newFieldData[field.name] = field.defaultValue; } } }); return newFieldData; } function handleClosePopup() { setShowPopup(false); } const handleButtonClick = () => { setShowPopup(!showPopup); }; const handleValidate = (model: any) => { modelRef.current = model; setShowPopup(!showPopup); }; function handleLoadModel() { modelRef.current = modelInput; } const NodeIconComponent = getIconComponent("ReplicateLogo"); return !data.nodeLoaded ? ( {!modelRef.current ? (
{showPopup && ( )}

{t("Or")}

) : ( <> )}
) : ( ); } ================================================ FILE: packages/ui/src/components/nodes/TransitionNode.tsx ================================================ import React, { useContext, useEffect, useMemo, useState } from "react"; import { Position, NodeProps, useUpdateNodeInternals } from "reactflow"; import { generateIdForHandle } from "../../utils/flowUtils"; import { NodeContext } from "../../providers/NodeProvider"; import { useIsPlaying } from "../../hooks/useIsPlaying"; import NodePlayButton from "./node-button/NodePlayButton"; import HandleWrapper from "../handles/HandleWrapper"; import useHandlePositions from "../../hooks/useHandlePositions"; import { GenericNodeData } from "./types/node"; interface TransitionNodeData extends GenericNodeData { handles: any; id: string; name: string; processorType: string; nbOutput: number; input: string; input_key: string; outputData?: string[]; lastRun: string; } interface TransitionNodeProps extends NodeProps { data: TransitionNodeData; } const TransitionNode: React.FC = React.memo( ({ data, id }) => { const { onUpdateNodeData } = useContext(NodeContext); const [nodeId, setNodeId] = useState(`${data.name}-${Date.now()}`); const [isPlaying, setIsPlaying] = useIsPlaying(); const updateNodeInternals = useUpdateNodeInternals(); const outputHandleId = useMemo(() => generateIdForHandle(0, true), []); const inputHandleId = useMemo(() => generateIdForHandle(0), []); const { allHandlePositions } = useHandlePositions(data, 1, [ outputHandleId, ]); useEffect(() => { setNodeId(`${data.name}-${Date.now()}`); setIsPlaying(false); updateNodeInternals(id); }, [data.lastRun]); const handlePlayClick = () => { setIsPlaying(true); }; const handleChangeHandlePosition = ( newPosition: Position, handleId: string, ) => { onUpdateNodeData(id, { ...data, handles: { ...data.handles, [handleId]: newPosition, }, }); updateNodeInternals(id); }; return (
); }, ); export default TransitionNode; ================================================ FILE: packages/ui/src/components/nodes/node-button/InputNameBar.tsx ================================================ import { memo } from "react"; import { Tooltip, ActionIcon } from "@mantine/core"; import { FaMinus, FaPlus } from "react-icons/fa"; import { useTranslation } from "react-i18next"; interface InputNameBarProps { inputNames: string[]; textareaRef: any; fieldToUpdate?: string; onNameClick?: (value: string) => void; addNewInput?: () => void; removeInput?: () => void; } function InputNameBar({ inputNames, textareaRef, fieldToUpdate, onNameClick, addNewInput, removeInput, }: InputNameBarProps) { const { t } = useTranslation("flow"); const insertAtCursor = ( textarea: HTMLTextAreaElement | null, myValue: string, ) => { if (textarea) { if (textarea.selectionStart || textarea.selectionStart === 0) { let startPos = textarea.selectionStart; let endPos = textarea.selectionEnd; textarea.value = textarea.value.substring(0, startPos) + myValue + textarea.value.substring(endPos, textarea.value.length); textarea.selectionStart = startPos + myValue.length; textarea.selectionEnd = startPos + myValue.length; } else { textarea.value += myValue; } } }; const handleNameClick = (name: string) => { if (!fieldToUpdate) { insertAtCursor(textareaRef?.current, `\${${name}} `); } onNameClick?.(`\${${name}} `); }; const handleAddInput = () => { if (addNewInput) addNewInput(); }; return (
{inputNames.map((name) => (
handleNameClick(name)} > {name}
))} {!!handleAddInput && !!removeInput && ( <> {inputNames.length > 2 && ( )} )}
); } export default memo(InputNameBar); ================================================ FILE: packages/ui/src/components/nodes/node-button/NodePlayButton.tsx ================================================ import React, { useContext, useState } from "react"; import styled, { css, keyframes } from "styled-components"; import { FaCheck, FaPlay, FaStop } from "react-icons/fa"; import { NodeContext } from "../../../providers/NodeProvider"; import TapScale from "../../shared/motions/TapScale"; import * as NodeStyles from "../Node.styles"; interface NodePlayButtonProps { isPlaying?: boolean; hasRun?: boolean; onClick?: () => void; nodeName: string; size?: "small" | "medium" | "large"; } const NodePlayButton: React.FC = ({ isPlaying, hasRun, onClick, nodeName, size, }) => { const { runNode, isRunning, currentNodesRunning } = useContext(NodeContext); const [isHovered, setHovered] = useState(false); const handleClick = () => { if (!isPlaying) { if (runNode(nodeName) && onClick) { onClick(); } } }; const handleMouseEnter = () => setHovered(true); const handleMouseLeave = () => setHovered(false); const isCurrentNodeRunning = currentNodesRunning.includes(nodeName); const isDisabled = isCurrentNodeRunning && !isHovered; const IconComponent = getIconComponent( isPlaying, isCurrentNodeRunning, hasRun, isHovered, ); const tailwindClassSize = { small: "text-sm", medium: "text-md", large: "text-3xl", }[size || "large"]; return ( ); }; function getIconComponent( isPlaying: boolean | undefined, isCurrentNodeRunning: boolean, hasRun: boolean | undefined, isHovered: boolean, ) { if (isPlaying || isCurrentNodeRunning) return NodeStyles.LoadingIcon; if (hasRun && !isHovered) return CheckIcon; return isCurrentNodeRunning ? NodeStopButtonIcon : NodePlayButtonIcon; } const NodePlayButtonContainer = styled.button<{ disabled?: boolean }>` cursor: pointer; color: ${(props) => (props.disabled ? "#888" : "#7bb380")}; &:hover { color: ${(props) => (props.disabled ? "#888" : "#57ff2d")}; } `; const NodePlayButtonIcon = styled(FaPlay)` transition: transform 0.3s ease-in-out; `; const NodeStopButtonIcon = styled(FaStop)` transition: transform 0.3s ease-in-out; `; const CheckIcon = styled(FaCheck)` transition: transform 0.3s ease-in-out; `; export default NodePlayButton; ================================================ FILE: packages/ui/src/components/nodes/node-input/FileUploadField.tsx ================================================ import React, { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { getUploadAndDownloadUrl, uploadWithS3Link, } from "../../../api/uploadFile"; import { useLoading } from "../../../hooks/useLoading"; import { toastErrorMessage } from "../../../utils/toastUtils"; import { getOutputExtension } from "../../nodes/node-output/outputUtils"; import { LoadingSpinner } from "../../nodes/Node.styles"; import { Input } from "@mantine/core"; import OutputRenderer from "./OutputRenderer"; import { MdFileUpload } from "react-icons/md"; import { ThemeContext } from "../../../providers/ThemeProvider"; export interface UploadInfo { url: string; extension: string; } interface FileUploadFieldProps { onFileUpload: (uploadResult: UploadInfo) => void; onUrlSubmit: (url: string) => void; value?: string; isRenderForNode?: boolean; } const FileUploadField: React.FC = ({ onFileUpload, onUrlSubmit, value = "", isRenderForNode = false, }) => { const { t } = useTranslation("flow"); const [url, setUrl] = useState(value); const [showPreview, setShowPreview] = useState(!!value); const [isLoading, startLoadingWith] = useLoading(); const fileInputRef = useRef(null); const { getStyle } = useContext(ThemeContext); useEffect(() => { if (value) { setUrl(value); setShowPreview(true); } }, [value]); const handleFileButtonClick = () => { fileInputRef.current?.click(); }; const handleFileChange = async ( event: React.ChangeEvent, ) => { const file = event.target.files?.[0]; if (file) { try { const result = await startLoadingWith(uploadFile, file); const outputType = getOutputExtension(file.name); const info: UploadInfo = { url: result.download_link, extension: outputType, }; onFileUpload(info); setUrl(info.url); setShowPreview(true); } catch (error) { toastErrorMessage(t("error.upload_failed")); } } }; const uploadFile = async (file: File) => { const filename = file.name; const urls = await getUploadAndDownloadUrl(filename); await uploadWithS3Link(urls.upload_data, file); return urls; }; const handleBlur = () => { if (url) { onUrlSubmit(url); setShowPreview(true); } }; return (
{isLoading && (
)} {!isLoading && (
setUrl(e.target.value)} onBlur={handleBlur} size="md" classNames={{ input: "text-md", }} styles={ isRenderForNode ? { input: { backgroundColor: getStyle()?.nodeInputBg, color: getStyle()?.text, }, } : undefined } />
)} {showPreview && (
)}
); }; export default FileUploadField; ================================================ FILE: packages/ui/src/components/nodes/node-input/ImageMaskCreator.tsx ================================================ import { Button, Slider } from "@mantine/core"; import React, { useState, useRef, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { ReactSketchCanvas, ReactSketchCanvasRef } from "react-sketch-canvas"; import { MdRemove, MdUndo } from "react-icons/md"; import { BsFillEraserFill } from "react-icons/bs"; interface ImageMaskCreatorProps { onSave: (maskDataUrl: string) => void; imageUrls?: string[]; } export const ImageMaskCreator: React.FC = ({ onSave, imageUrls = [], }) => { const { t } = useTranslation("flow"); const [activeTab, setActiveTab] = useState<"url" | "select" | "custom">( imageUrls.length > 0 ? "select" : "url", ); const [imageUrl, setImageUrl] = useState(""); const [penSize, setPenSize] = useState(10); const [originalWidth, setOriginalWidth] = useState(800); const [originalHeight, setOriginalHeight] = useState(600); const [displayWidth, setDisplayWidth] = useState(800); const [displayHeight, setDisplayHeight] = useState(600); const [scaleFactor, setScaleFactor] = useState(1); const [customWidth, setCustomWidth] = useState(800); const [customHeight, setCustomHeight] = useState(600); const canvasRef = useRef(null); // Listen for image load changes to adjust canvas dimensions useEffect(() => { if ((activeTab === "url" || activeTab === "select") && imageUrl) { const img = new Image(); img.crossOrigin = "anonymous"; img.src = imageUrl; img.onload = () => { calculateDimensions(img.width, img.height); }; } else if (activeTab === "custom") { calculateDimensions(customWidth, customHeight); } }, [imageUrl, customWidth, customHeight, activeTab]); // Undo feature: Listen for Ctrl+Z / Cmd+Z to remove the last stroke useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") { event.preventDefault(); // Prevent default undo behavior if (canvasRef.current) { canvasRef.current.undo(); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, []); const calculateDimensions = (width: number, height: number) => { const maxWidth = 800; // Maximum display width const maxHeight = 600; // Maximum display height let scale = 1; if (width > maxWidth || height > maxHeight) { // Calculate scale factor to fit the image within the modal const widthScale = maxWidth / width; const heightScale = maxHeight / height; scale = Math.min(widthScale, heightScale); } setOriginalWidth(width); setOriginalHeight(height); setDisplayWidth(width * scale); setDisplayHeight(height * scale); setScaleFactor(scale); }; const handleSave = async () => { if (canvasRef.current) { try { const paths = await canvasRef.current.exportPaths(); const tempCanvas = document.createElement("canvas"); tempCanvas.width = originalWidth; tempCanvas.height = originalHeight; const context = tempCanvas.getContext("2d"); if (context) { // Fill background with black context.fillStyle = "black"; context.fillRect(0, 0, originalWidth, originalHeight); // Set stroke style to white context.strokeStyle = "white"; context.lineCap = "round"; // Redraw the paths onto the temp canvas paths.forEach((stroke) => { context.lineWidth = stroke.strokeWidth / scaleFactor; context.beginPath(); stroke.paths.forEach((point: any, idx: number) => { const x = point.x / scaleFactor; const y = point.y / scaleFactor; if (idx === 0) { context.moveTo(x, y); } else { context.lineTo(x, y); } }); context.stroke(); }); // Export the mask image const dataUrl = tempCanvas.toDataURL(); onSave(dataUrl); } } catch (e) { console.log(e); } } }; const handleClearCanvas = () => { if (canvasRef.current) { canvasRef.current.clearCanvas(); } }; const handleUndo = () => { if (canvasRef.current) { canvasRef.current.undo(); } }; const handleImageUrlChange = (e: React.ChangeEvent) => { setImageUrl(e.target.value); }; const handleSelectImageUrl = (url: string) => { setImageUrl(url); }; return (
{/* Tabs for selecting input method */}
{imageUrls.length > 0 && ( )}
{/* Content based on active tab */} {activeTab === "url" && (
)} {activeTab === "select" && imageUrls.length > 0 && (

{t("SelectAnImage")}

{imageUrls.map((url, index) => ( {t("PreviewImage", handleSelectImageUrl(url)} /> ))}
)} {activeTab === "custom" && (
setCustomWidth(Number(e.target.value))} className="bg-af-bg-1 mt-1 block w-full rounded-md border border-gray-700 px-3 py-2 focus:border-teal-400 focus:ring-teal-400" min="1" />
setCustomHeight(Number(e.target.value))} className="bg-af-bg-1 mt-1 block w-full rounded-md border border-gray-700 px-3 py-2 focus:border-teal-400 focus:ring-teal-400" min="1" />
)}
{(imageUrl || activeTab === "custom") && (
{/* Pen Size, Clear and Undo Buttons */}
{penSize}px
{/* Canvas */}
)}
); }; ================================================ FILE: packages/ui/src/components/nodes/node-input/ImageMaskCreatorField.tsx ================================================ import { Button, Modal } from "@mantine/core"; import React, { useContext, useEffect, useState } from "react"; import { ImageMaskCreator } from "./ImageMaskCreator"; import { useLoading } from "../../../hooks/useLoading"; import { getUploadAndDownloadUrl, uploadWithS3Link, } from "../../../api/uploadFile"; import { LoadingSpinner } from "../Node.styles"; import { useTranslation } from "react-i18next"; import DefaultPopupWrapper from "../../popups/DefaultPopup"; import { ThemeContext } from "../../../providers/ThemeProvider"; interface ImageMaskCreatorFieldProps { onChange: (value: string) => void; imageUrls?: string[]; loadImageUrls?: () => Promise; } export default function ImageMaskCreatorField({ onChange, imageUrls, loadImageUrls, }: ImageMaskCreatorFieldProps) { const { t } = useTranslation("flow"); const { dark } = useContext(ThemeContext); const [isModalOpen, setIsModalOpen] = useState(false); const [maskPreview, setMaskPreview] = useState(null); const [isLoading, startLoadingWith] = useLoading(); const [imageUrlsState, setImageUrls] = useState(imageUrls ?? []); useEffect(() => { if (loadImageUrls && isModalOpen) { startLoadingWith(async () => { const urls = await loadImageUrls(); setImageUrls(urls); }); } }, [loadImageUrls, isModalOpen]); function dataURLtoFile(dataUrl: string, filename: string): File { const arr = dataUrl.split(","); const mimeMatch = arr[0].match(/:(.*?);/); const mime = mimeMatch ? mimeMatch[1] : ""; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename, { type: mime }); } async function uploadFile(files: File[]) { const filename = files[0].name; const urls = await getUploadAndDownloadUrl(filename); const uploadData = urls.upload_data; await uploadWithS3Link(uploadData, files[0]); return urls; } const handleSaveMask = async (maskDataUrl: string) => { setIsModalOpen(false); const maskFile = dataURLtoFile(maskDataUrl, "mask.png"); const files: File[] = [maskFile]; try { const urls = await startLoadingWith(uploadFile, files); if (urls.download_link) { setMaskPreview(urls.download_link); onChange(urls.download_link); } else { alert(t("FailedToUploadImage")); } } catch (error) { alert(t("FailedToUploadImage")); } }; return (
{isModalOpen && ( setIsModalOpen(false)} popupClassNames="overflow-auto w-[85%] md:w-[75%] max-h-[95%] flex shadow-lg md:p-4 p-2 rounded-md mt-[2%]" style={{ background: dark ? "linear-gradient(135deg, #101113, #1a1b1e)" : "#FFFFFF", }} >
)} {isLoading && (
)} {maskPreview && (

{t("Preview")}

{t("MaskPreview")
)}
); } ================================================ FILE: packages/ui/src/components/nodes/node-input/ImageMaskCreatorFieldFlowAware.tsx ================================================ import { getOutputExtension } from "../node-output/outputUtils"; import ImageMaskCreatorField from "./ImageMaskCreatorField"; interface ImageMaskCreatorFieldProps { onChange: (value: string) => void; } const extractImageUrls = (nodes: any[]) => { return nodes .flatMap((node) => { const outputData = node.data.outputData; if (typeof outputData === "string") return [outputData]; if (Array.isArray(outputData)) return outputData; return []; }) .filter((url) => getOutputExtension(url) === "imageUrl"); }; export default function ImageMaskCreatorFieldFlowAware({ onChange, }: ImageMaskCreatorFieldProps) { return ; } ================================================ FILE: packages/ui/src/components/nodes/node-input/KeyValueInputList.tsx ================================================ // KeyValueInputList.tsx import React from "react"; import { Button, Group, TextInput } from "@mantine/core"; import { MdClear } from "react-icons/md"; import { FaPlus } from "react-icons/fa"; interface KeyValuePair { key: string; value: string; } interface KeyValueInputListProps { pairs: KeyValuePair[]; onChange: (pairs: KeyValuePair[]) => void; } export const KeyValueInputList: React.FC = ({ pairs, onChange, }) => { const handleKeyChange = (index: number, newKey: string) => { const newPairs = [...pairs]; newPairs[index].key = newKey; onChange(newPairs); }; const handleValueChange = (index: number, newValue: string) => { const newPairs = [...pairs]; newPairs[index].value = newValue; onChange(newPairs); }; const handleAddPair = () => { onChange([...pairs, { key: "", value: "" }]); }; const handleRemovePair = (index: number) => { const newPairs = pairs.filter((_, i) => i !== index); onChange(newPairs); }; return (
{!!pairs && pairs.map((pair, index) => ( handleKeyChange(index, event.currentTarget.value) } style={{ flex: 1 }} /> handleValueChange(index, event.currentTarget.value) } style={{ flex: 1 }} /> handleRemovePair(index)} /> ))}
); }; ================================================ FILE: packages/ui/src/components/nodes/node-input/NodeField.tsx ================================================ import { InputHandle, NodeLabel } from "../Node.styles"; import { Position } from "reactflow"; import { Field } from "../../../nodes-configuration/types"; import { DisplayParams } from "../../../hooks/useFormFields"; import { FiFile, FiInfo, FiPlus, FiTrash } from "react-icons/fi"; import { useTranslation } from "react-i18next"; import { Tooltip } from "@mantine/core"; interface NodeFieldProps { field: T; renderField: (field: T, isLoopField?: boolean) => JSX.Element; label: string; handleId?: string; displayParams?: DisplayParams; handlePosition?: Position; onAddNewField?: () => void; onDeleteField?: () => void; } function NodeField< T extends Pick< Field, | "required" | "label" | "hasHandle" | "isLinked" | "description" | "hidden" | "type" >, >({ field, displayParams, renderField, label, handlePosition = Position.Left, handleId, onAddNewField, onDeleteField, }: NodeFieldProps) { const { t } = useTranslation("flow"); return ( <> {field.label && displayParams?.showLabels && (
{field.hasHandle && displayParams?.showHandles && ( )}
{label} {field.type === "fileUpload" && } {field.required ? * : null}
{!!field.description && ( )}
)} {!field.isLinked && (
{renderField(field)}
)} {onAddNewField && (
{onDeleteField && ( )}
)} ); } export default NodeField; ================================================ FILE: packages/ui/src/components/nodes/node-input/NodeTextField.tsx ================================================ import styled from "styled-components"; import { ChangeEvent, RefObject, useState } from "react"; import TextAreaPopupWrapper from "./TextAreaPopupWrapper"; interface NodeTextFieldProps { value: string; onChange: (event: ChangeEvent) => void; onChangeValue?: (value: string) => void; placeholder?: string; error?: boolean; isTouchDevice?: boolean; fieldName?: string; withEditPopup?: boolean; ref?: RefObject; } export default function NodeTextField({ value, onChange, onChangeValue, placeholder, error, isTouchDevice, fieldName, withEditPopup, ref, }: NodeTextFieldProps) { const [isTextareaSelected, setIsTextareaSelected] = useState(false); function handleChangeValue(value: string) { if (onChangeValue) { onChangeValue(value); } } const handleFocus = () => { setIsTextareaSelected(true); }; const handleBlur = () => { setIsTextareaSelected(false); }; const isControlled = !isTextareaSelected; if (!withEditPopup) { return ( onChange(event)} placeholder={placeholder} onFocus={handleFocus} onBlur={handleBlur} /> ); } return ( onChange(event)} placeholder={placeholder} onFocus={handleFocus} onBlur={handleBlur} ref={ref} /> ); } const NodeInput = styled.input` width: 100%; border: none; outline: none; font-size: 1.1em; color: ${({ theme }) => theme.text}; background-color: ${({ theme }) => theme.nodeInputBg}; padding: 12px 18px; border-radius: 8px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); transition: all ease-in-out; &:focus { border: solid; border-color: rgba(223, 223, 223, 0.175); } `; ================================================ FILE: packages/ui/src/components/nodes/node-input/NodeTextarea.tsx ================================================ import { useTranslation } from "react-i18next"; import { DisplayParams } from "../../../hooks/useFormFields"; import { Field } from "../../../nodes-configuration/types"; import { StyledNodeTextarea } from "../Node.styles"; import { GenericNodeData } from "../types/node"; import { useEffect, useState } from "react"; import TextAreaPopupWrapper from "./TextAreaPopupWrapper"; import { useReactFlow } from "reactflow"; interface NodeTextareaProps { data: GenericNodeData; field: Field; displayParams?: DisplayParams; id: string; textareaRef: React.RefObject; isTouchDevice: boolean; withMinHeight: boolean; onEventNodeDataChange: (event: any) => void; onNodeDataChange: (fieldName: string, value: any, target?: any) => void; } export default function NodeTextarea({ data, field, id, textareaRef, isTouchDevice, withMinHeight, onEventNodeDataChange, onNodeDataChange, }: NodeTextareaProps) { const { t } = useTranslation("flow"); let reactFlowInstance: any; try { reactFlowInstance = useReactFlow(); //TMP } catch (error) { //Do nothing } const [isTextareaSelected, setIsTextareaSelected] = useState(false); useEffect(() => { const maxHeight = 1000; const textarea = textareaRef.current; if (textarea) { textarea.style.height = "auto"; const rect = textarea.getBoundingClientRect(); const currentZoom = !!reactFlowInstance ? reactFlowInstance.getZoom() : 1; const adjustedTop = rect.top / currentZoom; const availableSpaceBelow = window.innerHeight / currentZoom - adjustedTop; const allowedHeight = Math.min( textarea.scrollHeight, maxHeight, availableSpaceBelow, ); textarea.style.height = `${allowedHeight}px`; } }, [data[field.name]]); const handleFocus = () => { setIsTextareaSelected(true); }; const handleBlur = () => { setIsTextareaSelected(false); }; const isControlled = !isTextareaSelected; return ( onNodeDataChange(field.name, value)} initValue={data[field.name]} fieldName={field.name} > ); } ================================================ FILE: packages/ui/src/components/nodes/node-input/OutputRenderer.tsx ================================================ import { useState } from "react"; import { FiFile } from "react-icons/fi"; import AudioUrlOutput from "../../nodes/node-output/AudioUrlOutput"; import ImageUrlOutput from "../../nodes/node-output/ImageUrlOutput"; import MarkdownOutput from "../../nodes/node-output/MarkdownOutput"; import PdfUrlOutput from "../../nodes/node-output/PdfUrlOutput"; import ThreeDimensionalUrlOutput from "../../nodes/node-output/ThreeDimensionalUrlOutput"; import VideoUrlOutput from "../../nodes/node-output/VideoUrlOutput"; import { NodeData } from "../../nodes/types/node"; import { OutputType } from "../../../nodes-configuration/types"; import OutputDisplay from "../../nodes/node-output/OutputDisplay"; import { useTranslation } from "react-i18next"; interface OutputRendererProps { data: NodeData; thumbnail?: boolean; showOutputOptions?: boolean; fontSize?: number; } export default function OutputRenderer({ data, thumbnail, showOutputOptions = true, fontSize = 1, }: OutputRendererProps) { const { t } = useTranslation("flow"); const [indexDisplayed, setIndexDisplayed] = useState(0); const getOutputComponent = (data: NodeData, outputType: OutputType) => { if (!data.outputData) return <>; let output = data.outputData; if (typeof output !== "string") { output = output[indexDisplayed]; } switch (outputType) { case "imageUrl": if (thumbnail) { return ; } return (
); case "videoUrl": return ; case "audioUrl": return ; case "3dUrl": return ; case "pdfUrl": return ; case "fileUrl": return (

{t("FileUploaded")}

); default: return ( 200 ? output.substring(0, 200) + "..." : output } name={data.name} appearance={{ fontSize: fontSize, }} /> ); } }; return ( ); } ================================================ FILE: packages/ui/src/components/nodes/node-input/TextAreaPopupWrapper.tsx ================================================ import { Tooltip } from "@mantine/core"; import { FiExternalLink } from "react-icons/fi"; import { TextareaModal } from "../utils/TextareaModal"; import { useState } from "react"; interface TextAreaPopupWrapperProps { children: React.ReactNode; onChange: (value: string) => void; initValue: string; fieldName?: string; } function TextAreaPopupWrapper({ children, onChange, initValue, fieldName, }: TextAreaPopupWrapperProps) { const [modalOpen, setModalOpen] = useState(false); function openModal() { setModalOpen(true); } function closeModal() { setModalOpen(false); } return ( <>
{children}
{modalOpen && ( )} ); } export default TextAreaPopupWrapper; ================================================ FILE: packages/ui/src/components/nodes/node-output/AudioUrlOutput.tsx ================================================ import React from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; import { getFileTypeFromUrl, getGeneratedFileName } from "./outputUtils"; import VideoJS from "../../players/VideoJS"; interface AudioUrlOutputProps { url: string; name: string; } const AudioUrlOutput: React.FC = ({ url, name }) => { const playerRef = React.useRef(null); const videoJsOptions = { controls: true, autoplay: false, loop: false, muted: false, fluid: true, bigPlayButton: false, plugins: { wavesurfer: { backend: "MediaElement", displayMilliseconds: false, debug: false, waveColor: "rgb(72, 159, 159)", progressColor: "rgba(32, 32, 32, 0.719)", cursorColor: "rgba(226, 226, 226, 0.616)", hideScrollbar: true, autoplay: false, height: "auto", }, }, }; const handlePlayerReady = (player: any) => { playerRef.current = player; const mimeType = `audio/${getFileTypeFromUrl(url)}`; player.src({ src: url, type: mimeType }); }; const handleDownloadClick = (event: React.MouseEvent) => { event.stopPropagation(); const link = document.createElement("a"); link.href = url; link.download = getGeneratedFileName(url, name); link.target = "_blank"; link.click(); }; return (
); }; const OutputAudioContainer = styled.div` display: flex; justify-content: center; align-items: center; position: relative; margin-top: 10px; `; export default AudioUrlOutput; ================================================ FILE: packages/ui/src/components/nodes/node-output/ImageBase64Output.tsx ================================================ import React, { memo } from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; interface ImageBase64OutputProps { data: string; name: string; lastRun?: string; } const ImageBase64Output: React.FC = ({ data, name, lastRun, }) => { const blob = new Blob([ new Uint8Array( atob(data) .split("") .map(function (c) { return c.charCodeAt(0); }), ), ]); const url = URL.createObjectURL(blob); const handleDownloadClick = () => { const link = document.createElement("a"); link.href = url; link.download = name + "-output-generated.jpg"; link.target = "_blank"; link.click(); }; return ( ); }; const OutputImageContainer = styled.div` position: relative; margin-top: 10px; `; const OutputImage = styled.img` display: block; width: 100%; height: auto; border-radius: 8px; `; const DownloadButton = styled.a` position: absolute; top: 8px; right: 8px; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background-color: #4285f4; color: #fff; border-radius: 50%; cursor: pointer; transition: background-color 0.3s ease; &:hover { background-color: #0d47a1; } `; function arePropsEqual( prevProps: ImageBase64OutputProps, nextProps: ImageBase64OutputProps, ) { return prevProps.lastRun === nextProps.lastRun; } export default memo(ImageBase64Output, arePropsEqual); ================================================ FILE: packages/ui/src/components/nodes/node-output/ImageUrlOutput.tsx ================================================ import React, { useEffect, useState } from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; import { getGeneratedFileName } from "./outputUtils"; import { toastErrorMessage } from "../../../utils/toastUtils"; import { useTranslation } from "react-i18next"; interface ImageUrlOutputProps { url: string; name: string; } const ImageUrlOutput: React.FC = ({ url, name }) => { const { t } = useTranslation("flow"); const [hasError, setHasError] = useState(false); useEffect(() => { setHasError(false); }, [url]); const handleDownloadClick = (event: React.MouseEvent) => { event.stopPropagation(); if (hasError) { toastErrorMessage("URL Expired"); return; } const link = document.createElement("a"); link.href = url; link.download = getGeneratedFileName(url, name); link.target = "_blank"; link.click(); }; const handleError = () => { setHasError(true); }; const handleLoad = () => { setHasError(false); }; return ( {hasError ? (

{t("ExpiredURL")}

) : ( <>
)}
); }; const OutputImageContainer = styled.div` position: relative; margin-top: 10px; `; const OutputImage = styled.img` display: block; width: 100%; height: auto; border-radius: 8px; `; export default ImageUrlOutput; ================================================ FILE: packages/ui/src/components/nodes/node-output/MarkdownOutput.tsx ================================================ import React, { memo, useContext, useMemo } from "react"; import remarkGfm from "remark-gfm"; import ReactMarkdown from "react-markdown"; import styled from "styled-components"; import "github-markdown-css"; import { FiCopy, FiMinus, FiPlus } from "react-icons/fi"; import { copyToClipboard } from "../../../utils/navigatorUtils"; import { toastFastInfoMessage } from "../../../utils/toastUtils"; import { useTranslation } from "react-i18next"; import { NodeContext } from "../../../providers/NodeProvider"; import { NodeAppearance } from "../types/node"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { tomorrow as theme } from "react-syntax-highlighter/dist/esm/styles/prism"; interface MarkdownOutputProps { data: string; name: string; appearance?: NodeAppearance; } const MarkdownOutput: React.FC = ({ data, name, appearance, }) => { const { t } = useTranslation("flow"); const { updateNodeAppearance } = useContext(NodeContext); const fontSize = appearance?.fontSize ?? 1.2; const stringifiedData = useMemo(() => { if (!data) return ""; return typeof data === "string" ? data : JSON.stringify(data); }, [data]); if (!data) return

; const increaseFontSize = () => { updateNodeAppearance(name, { ...appearance, fontSize: fontSize + 0.1, }); }; const decreaseFontSize = () => updateNodeAppearance(name, { ...appearance, fontSize: fontSize - 0.1, }); const handleCopyToClipboard = (event: any) => { event.stopPropagation(); if (data) { copyToClipboard(data); toastFastInfoMessage(t("CopiedToClipboard")); } }; const handleElementCopyToClipboard = (element: any) => { if (element) { copyToClipboard(element); toastFastInfoMessage(t("CopiedToClipboard")); } }; return (
{match[1]}
{ e.stopPropagation(); handleElementCopyToClipboard(children); }} onTouchStart={(e) => { e.stopPropagation(); handleElementCopyToClipboard(children); }} className="copy-icon" aria-label="Copy text" title="Copy text" >
Copy
) : ( {children} ); }, }} /> { e.stopPropagation(); }} > { e.stopPropagation(); increaseFontSize(); }} onTouchStart={(e) => { e.stopPropagation(); increaseFontSize(); }} aria-label="Increase text size" title="Increase text size" > { e.stopPropagation(); decreaseFontSize(); }} onTouchStart={(e) => { e.stopPropagation(); decreaseFontSize(); }} aria-label="Decrease text size" title="Decrease text size" > { e.stopPropagation(); handleCopyToClipboard(e); }} onTouchStart={(e) => { e.stopPropagation(); handleCopyToClipboard(e); }} className="copy-icon" aria-label="Copy text" title="Copy text" >
); }; const IconButton = styled.div` cursor: pointer; transition: color 0.2s; color: #ffffff80; &:hover { color: #ffffff; } `; const IconContainer = styled.div` position: absolute; display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 8px; top: 0.5em; right: 0.1em; `; const StyledReactMarkdown = styled(ReactMarkdown)<{ fontSize: number }>` background-color: transparent !important; color: #f5f5f5; font-size: ${(props) => props.fontSize}em; user-select: text; `; export const MemoizedStyledReactMarkdown = memo( StyledReactMarkdown, (prevProps, nextProps) => { return ( prevProps.children === nextProps.children && prevProps.fontSize === nextProps.fontSize ); }, ); export default memo(MarkdownOutput); ================================================ FILE: packages/ui/src/components/nodes/node-output/NodeOutput.tsx ================================================ import { copyToClipboard } from "../../../utils/navigatorUtils"; import { NodeLogs, NodeLogsText } from "../Node.styles"; import { NodeData } from "../types/node"; import { toastFastInfoMessage } from "../../../utils/toastUtils"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { FiCopy } from "react-icons/fi"; import { getOutputExtension } from "./outputUtils"; import { OutputType } from "../../../nodes-configuration/types"; import OutputDisplay from "./OutputDisplay"; interface NodeOutputProps { data: NodeData; showLogs: boolean; onClickOutput: () => void; } export default function NodeOutput({ data, showLogs, onClickOutput, }: NodeOutputProps) { const { t } = useTranslation("flow"); function getOutputType(): OutputType { if (data.config?.outputType) { return data.config.outputType; } if (!data.outputData) { return "markdown"; } let outputData = data.outputData; let output = ""; if (typeof outputData !== "string") { output = outputData[0]; } else { output = outputData; } const outputType = getOutputExtension(output); return outputType; } const outputType = getOutputType(); const outputIsMedia = (outputType === "imageUrl" || outputType === "imageBase64" || outputType === "videoUrl" || outputType === "audioUrl" || outputType === "pdfUrl" || outputType === "3dUrl") && !!data.outputData; return ( {!showLogs && data.outputData ? ( {t("ClickToShowOutput")} ) : ( )} ); } ================================================ FILE: packages/ui/src/components/nodes/node-output/OutputDisplay.tsx ================================================ import MarkdownOutput from "./MarkdownOutput"; import { NodeData } from "../types/node"; import { useTranslation } from "react-i18next"; import { FiFile } from "react-icons/fi"; import ImageUrlOutput from "./ImageUrlOutput"; import ImageBase64Output from "./ImageBase64Output"; import VideoUrlOutput from "./VideoUrlOutput"; import AudioUrlOutput from "./AudioUrlOutput"; import { getOutputExtension } from "./outputUtils"; import PdfUrlOutput from "./PdfUrlOutput"; import { OutputType } from "../../../nodes-configuration/types"; import { useState } from "react"; import ThreeDimensionalUrlOutput from "./ThreeDimensionalUrlOutput"; interface OutputDisplayProps { data: NodeData; getOutputComponentOverride?: ( data: NodeData, outputType: OutputType, ) => JSX.Element | null; } export default function OutputDisplay({ data, getOutputComponentOverride, }: OutputDisplayProps) { const { t } = useTranslation("flow"); const [indexDisplayed, setIndexDisplayed] = useState(0); const nbOutput = data.outputData != null && typeof data.outputData !== "string" ? data.outputData.length : 1; const getOutputComponent = () => { if (getOutputComponentOverride) { const override = getOutputComponentOverride(data, getOutputType()); if (override) { return override; } } if (!data.outputData) return <>; let output = data.outputData; if (typeof output !== "string") { output = output[indexDisplayed]; } switch (getOutputType()) { case "imageUrl": return ; case "imageBase64": return ( ); case "videoUrl": return ; case "audioUrl": return ; case "3dUrl": return ; case "pdfUrl": return ; case "fileUrl": return (

{t("FileUploaded")}

); default: return ( ); } }; function getOutputType(): OutputType { if (data.config?.outputType) { return data.config.outputType; } if (!data.outputData) { return "markdown"; } let outputData = data.outputData; let output = ""; if (typeof outputData !== "string") { output = outputData[indexDisplayed]; } else { output = outputData; } const outputType = getOutputExtension(output); return outputType; } return (
{nbOutput > 1 && typeof data.outputData !== "string" && (
{data?.outputData?.map((output, index) => (
)} {getOutputComponent()}
); } ================================================ FILE: packages/ui/src/components/nodes/node-output/PdfUrlOutput.tsx ================================================ import React from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; import { getGeneratedFileName } from "./outputUtils"; interface PdfUrlOutputProps { url: string; name: string; } const PdfUrlOutput: React.FC = ({ url, name }) => { const handleDownloadClick = (event: React.MouseEvent) => { event.stopPropagation(); const link = document.createElement("a"); link.href = url; link.download = getGeneratedFileName(url, name); // Ensure getGeneratedFileName handles PDF filenames correctly link.target = "_blank"; link.click(); }; return (

Your browser does not support PDFs. Please download the PDF to view it: Download PDF.

); }; const OutputPdfContainer = styled.div` position: relative; margin-top: 10px; padding-top: 56.25%; // Maintain aspect ratio for PDF viewer height: 0; // Use padding to define height based on the container's width overflow: hidden; `; const OutputPdf = styled.object` position: absolute; top: 0; left: 0; width: 100%; height: 100%; `; export default PdfUrlOutput; ================================================ FILE: packages/ui/src/components/nodes/node-output/ThreeDimensionalUrlOutput.tsx ================================================ import React, { useEffect, useRef, useState } from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; import { getFileTypeFromUrl, getGeneratedFileName } from "./outputUtils"; import { useTranslation } from "react-i18next"; import { Scene, PerspectiveCamera, WebGLRenderer, AmbientLight, DirectionalLight, } from "three"; import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { LoadingSpinner } from "../Node.styles"; interface ThreeDimensionalUrlOutputProps { url: string; name: string; } const ThreeDimensionalUrlOutput: React.FC = ({ url, name, }) => { const { t } = useTranslation("flow"); const [hasError, setHasError] = useState(false); const [isLoading, setIsLoading] = useState(true); const [height, setHeight] = useState("500px"); const containerRef = useRef(null); const threeContainerRef = useRef(null); const getParentContainer = () => { return containerRef?.current?.parentElement; }; const updateParentHeight = () => { if (containerRef.current && containerRef.current.parentElement) { const parentHeight = containerRef.current.parentElement.clientHeight; const newHeight = Math.max(parentHeight, 500); setHeight(`${newHeight}px`); } }; const loadObj = (url: string, scene: Scene) => { new OBJLoader().load( url, (obj: any) => { setIsLoading(false); setHasError(false); scene.add(obj); }, undefined, () => { setHasError(true); }, ); }; const loadGlb = (url: string, scene: Scene) => { new GLTFLoader().load( url, (gltf: any) => { setIsLoading(false); setHasError(false); scene.add(gltf.scene); }, undefined, () => { setHasError(true); }, ); }; useEffect(() => { updateParentHeight(); const container = getParentContainer(); if (!container) return; const type = getFileTypeFromUrl(url); const scene = new Scene(); const camera = new PerspectiveCamera( 75, container.clientWidth / container.clientHeight, 0.1, 1000, ); const renderer = new WebGLRenderer(); renderer.setSize(container.clientWidth, container.clientHeight); renderer.domElement.style.width = "100%"; renderer.domElement.style.height = "100%"; threeContainerRef.current?.appendChild(renderer.domElement); const ambientLight = new AmbientLight(0xffffff, 1); // soft white light scene.add(ambientLight); const directionalLight = new DirectionalLight(0xffffff, 2); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight); if (type === "obj") loadObj(url, scene); else loadGlb(url, scene); const controls = new OrbitControls(camera, renderer.domElement); const animate = () => { requestAnimationFrame(animate); controls.update(); renderer.clear(); renderer.render(scene, camera); }; camera.position.z = 5; animate(); return () => { controls.dispose(); renderer.dispose(); scene.children.forEach((child) => { scene.remove(child); }); scene.remove(); }; }, [url]); const handleDownloadClick = (event: React.MouseEvent) => { event.stopPropagation(); const link = document.createElement("a"); link.href = url; link.download = getGeneratedFileName(url, name); link.target = "_blank"; link.click(); }; if (hasError) { return

{t("ExpiredURL")}

; } return ( {isLoading ? ( ) : null}
e.stopPropagation()} /> {}
); }; const OutputContainer = styled.div` position: relative; align-items: center; justify-items: center; text-align: center; display: flex; margin-top: 10px; width: auto; `; export default ThreeDimensionalUrlOutput; ================================================ FILE: packages/ui/src/components/nodes/node-output/VideoUrlOutput.tsx ================================================ import React, { useEffect, useState } from "react"; import { FaDownload } from "react-icons/fa"; import styled from "styled-components"; import { getGeneratedFileName } from "./outputUtils"; import { useTranslation } from "react-i18next"; interface VideoUrlOutputProps { url: string; name: string; } const VideoUrlOutput: React.FC = ({ url, name }) => { const { t } = useTranslation("flow"); const [hasError, setHasError] = useState(false); useEffect(() => { setHasError(false); }, [url]); const handleDownloadClick = (event: React.MouseEvent) => { event.stopPropagation(); const link = document.createElement("a"); link.href = url; link.download = getGeneratedFileName(url, name); link.target = "_blank"; link.click(); }; const handleError = () => { setHasError(true); }; const handleLoad = () => { setHasError(false); }; return ( {hasError ? (

{t("ExpiredURL")}

) : ( <> {" "} {}
)}
); }; const OutputVideoContainer = styled.div` position: relative; margin-top: 10px; `; const OutputVideo = styled.video` display: block; width: 100%; height: auto; border-radius: 8px; `; export default VideoUrlOutput; ================================================ FILE: packages/ui/src/components/nodes/node-output/outputUtils.ts ================================================ import { OutputType } from "../../../nodes-configuration/types"; export const getFileExtension = (url: string) => { const extensionMatch = url.match(/\.([0-9a-z]+)(?:[\?#]|$)/i); return extensionMatch ? extensionMatch[1] : ""; }; export const getGeneratedFileName = (url: string, nodeName: string) => { const extension = getFileExtension(url); return `${nodeName}-output.${extension}`; }; const extensionToTypeMap: { [key: string]: OutputType } = { // Image extensions ".png": "imageUrl", ".jpg": "imageUrl", ".gif": "imageUrl", ".jpeg": "imageUrl", ".webp": "imageUrl", // Video extensions ".mp4": "videoUrl", ".mov": "videoUrl", // Audio extensions ".mp3": "audioUrl", ".wav": "audioUrl", // 3D extensions ".obj": "3dUrl", ".glb": "3dUrl", // Other extensions ".pdf": "fileUrl", ".txt": "fileUrl", }; export function getOutputExtension(output: string): OutputType { if (!output) return "markdown"; if (typeof output !== "string") return "markdown"; let extension = Object.keys(extensionToTypeMap).find((ext) => output.endsWith(ext), ); if (!extension) { extension = "." + getFileTypeFromUrl(output); } return extension ? extensionToTypeMap[extension] : "markdown"; } export function getFileTypeFromUrl(url: string) { const lastDotIndex = url.lastIndexOf("."); const urlWithoutParams = url.includes("?") ? url.substring(0, url.indexOf("?")) : url; const fileType = urlWithoutParams.substring(lastDotIndex + 1); return fileType; } ================================================ FILE: packages/ui/src/components/nodes/types/node.ts ================================================ import { NodeConfig, NodeSubConfig } from "../../../nodes-configuration/types"; export interface NodeInput { inputName: string; inputNode: string; inputNodeOutputKey: number; } export interface NodeAppearance { color?: string; customName?: string; fontSize?: number; } export interface NodeData { id: string; name: string; handles: any; processorType: string; nbOutput: number; inputs: NodeInput[]; outputData?: string[] | string; lastRun?: string; missingFields?: string[]; config: NodeConfig; appearance?: NodeAppearance; variantConfig?: NodeSubConfig; [key: string]: any; } export interface GenericNodeData extends NodeData { width?: number; height?: number; [key: string]: any; } ================================================ FILE: packages/ui/src/components/nodes/utils/HintComponent.tsx ================================================ import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; interface HintComponentProps { hintId: string; textVar: string; } const HintComponent: React.FC = ({ hintId, textVar }) => { const { t } = useTranslation("flow"); const [showHint, setShowHint] = useState(false); useEffect(() => { const storageKey = `hasHintBeenHidden-${hintId}`; const hasHintBeenHidden = localStorage.getItem(storageKey); if (hasHintBeenHidden) { setShowHint(false); } else { setShowHint(true); } }, [hintId]); const handleHideClick = () => { const storageKey = `hasHintBeenHidden-${hintId}`; localStorage.setItem(storageKey, "true"); setShowHint(false); }; return ( <> {showHint && (
{t(textVar)}
)} ); }; export default HintComponent; ================================================ FILE: packages/ui/src/components/nodes/utils/ImageModal.tsx ================================================ import ReactDOM from "react-dom"; import { FaTimes } from "react-icons/fa"; interface ImageModalProps { src: string; alt: string; onClose: () => void; } export function ImageModal({ src, alt, onClose }: ImageModalProps) { return ReactDOM.createPortal( <>
{alt}
, document.body, ); } ================================================ FILE: packages/ui/src/components/nodes/utils/ImageZoomable.tsx ================================================ import { useState } from "react"; import { FaSearchPlus, FaTimes } from "react-icons/fa"; import { ImageModal } from "./ImageModal"; interface ImageZoomableProps { src: string; alt: string; } export function ImageZoomable({ src, alt }: ImageZoomableProps) { const [isImageZoomed, setImageZoomed] = useState(false); const handleImageZoom = () => setImageZoomed(true); const handleCloseZoom = () => setImageZoomed(false); return ( <>
{alt}
{isImageZoomed && ( )} ); } ================================================ FILE: packages/ui/src/components/nodes/utils/NodeHelp.tsx ================================================ import { url } from "inspector"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { FaSearchPlus, FaTimes } from "react-icons/fa"; import { ImageZoomable } from "./ImageZoomable"; export type UrlWithLabel = { url: string; label: string; }; export type NodeHelpData = { description: string; imageUrl: string; docUrls: UrlWithLabel[]; }; interface NodeHelpProps { data: NodeHelpData; onClose: () => void; } export function NodeHelp({ data, onClose }: NodeHelpProps) { const { t } = useTranslation("flow"); if (!data || !data.description) { return (
{t("noDataAvailableForThisNode")}
); } return (
{data.imageUrl && ( )}

{data.description}

{!!data.docUrls && data.docUrls.length > 0 && ( <>

{t("learnMore")}

{data.docUrls.map((urlData, index) => ( {urlData.label ?? "More Info"} ))}
)}
); } ================================================ FILE: packages/ui/src/components/nodes/utils/NodeIcons.tsx ================================================ import { FC } from "react"; import { AiOutlineEdit, AiOutlineMergeCells, AiOutlineSearch, } from "react-icons/ai"; import { BiMask } from "react-icons/bi"; import { BsFiletypeJson, BsListTask, BsRegex } from "react-icons/bs"; import { GiPerspectiveDiceSix } from "react-icons/gi"; import { FaUserCircle, FaRobot, FaPlay, FaLink, FaFilm, FaImage, FaEye, FaAws, FaProjectDiagram, FaGoogle, FaRandom, } from "react-icons/fa"; import { SiZapier } from "react-icons/si"; import { FiFilter, FiRepeat } from "react-icons/fi"; import { MdHttp, MdLoop, MdOutlineBolt, MdOutlineCrop, MdSwapHoriz, } from "react-icons/md"; import { TbHttpGet } from "react-icons/tb"; const ICON_MAP: { [key: string]: FC } = { FaUserCircle: FaUserCircle, FaRobot: FaRobot, FaPlay: FaPlay, FaLink: FaLink, FaFilm: FaFilm, FaImage: FaImage, FaEye: FaEye, FiFilter: FiFilter, AiOutlineSearch: AiOutlineSearch, BsRegex: BsRegex, MdSwapHoriz: MdSwapHoriz, AiOutlineEdit: AiOutlineEdit, AiOutlineMergeCells: AiOutlineMergeCells, BsJson: BsFiletypeJson, FaAws: FaAws, TbHttpGet: TbHttpGet, MdHttp: MdHttp, MdOutlineCrop: MdOutlineCrop, BiMask: BiMask, FaProjectDiagram: FaProjectDiagram, FiRepeat: FiRepeat, BsListTask: BsListTask, SubflowLoop: () => (
), AIFlowLogo: () => hi, OpenAILogo: () => ( openai ), ReplicateLogo: () => ( replicate ), YoutubeLogo: () => ( youtube ), AnthropicLogo: () => ( anthropic ), StabilityAILogo: () => ( stabilityai ), AirTableLogo: () => ( airtable ), OpenRouterLogo: () => ( openrouter ), FaGoogle, ZapierIcon: () => , MakeIcon: () => ( make ), DeepSeekLogo: () => ( deepseek ), GeminiIcon: () => ( gemini ), FaRandom, GiPerspectiveDiceSix, }; export const getIconComponent = (type: string) => ICON_MAP[type]; ================================================ FILE: packages/ui/src/components/nodes/utils/TextareaModal.tsx ================================================ import { Button, Textarea } from "@mantine/core"; import { ChangeEvent, useState } from "react"; import ReactDOM from "react-dom"; import { useTranslation } from "react-i18next"; import { FaTimes } from "react-icons/fa"; interface TextareaModalProps { initValue: string; onChange: (value: string) => void; onClose: () => void; fieldName?: string; } export function TextareaModal({ initValue, fieldName, onChange, onClose, }: TextareaModalProps) { const { t } = useTranslation("flow"); const [value, setValue] = useState(initValue); function handleChange(event: ChangeEvent) { setValue(event.target.value); } function handleValidate() { onChange(value); onClose(); } return ReactDOM.createPortal( <>
e.stopPropagation()} >
{t("EditTextContent")}
{fieldName &&

{fieldName}

}