diff --git a/examples/runware-demo/esbuild/config.mjs b/examples/runware-demo/esbuild/config.mjs
new file mode 100644
index 00000000..6ee10d64
--- /dev/null
+++ b/examples/runware-demo/esbuild/config.mjs
@@ -0,0 +1,56 @@
+import chalk from 'chalk';
+import { readFile } from 'fs/promises';
+import dotenv from 'dotenv';
+
+import baseConfig from '../../../esbuild/config.mjs';
+import log from '../../../esbuild/log.mjs';
+
+// import packageJson from '../package.json' assert { type: 'json' };
+// Avoid the Experimental Feature warning when using the above.
+const packageJson = JSON.parse(
+ await readFile(new URL('../package.json', import.meta.url))
+);
+
+// Load .env.local only in local development (not in CI/Vercel)
+if (!process.env.CI && !process.env.VERCEL) {
+ dotenv.config({ path: '.env.local' });
+}
+
+export default ({ isDevelopment }) => {
+ log(
+ `${chalk.yellow('Building version:')} ${chalk.bold(packageJson.version)}`
+ );
+
+ // Base configuration that applies to all builds
+ const baseOptions = {
+ isDevelopment,
+ external: [],
+ pluginVersion: packageJson.version
+ };
+
+ // Get the base configuration
+ const config = baseConfig(baseOptions);
+
+ // Set entry points and output configuration
+ config.entryPoints = ['./src/index.ts'];
+ config.outExtension = { '.js': '.mjs' };
+ config.outdir = './dist';
+ config.outbase = './src';
+ config.outfile = undefined;
+ config.define = {
+ ...config.define,
+ 'process.env.CESDK_LICENSE': JSON.stringify(
+ 'CESDK_LICENSE' in process.env ? process.env.CESDK_LICENSE : ''
+ ),
+ 'process.env.RUNWARE_PROXY_URL': JSON.stringify(
+ 'RUNWARE_PROXY_URL' in process.env ? process.env.RUNWARE_PROXY_URL : ''
+ ),
+ 'process.env.ANTHROPIC_PROXY_URL': JSON.stringify(
+ 'ANTHROPIC_PROXY_URL' in process.env
+ ? process.env.ANTHROPIC_PROXY_URL
+ : ''
+ )
+ };
+
+ return config;
+};
diff --git a/examples/runware-demo/esbuild/global.d.ts b/examples/runware-demo/esbuild/global.d.ts
new file mode 100644
index 00000000..b2d2a926
--- /dev/null
+++ b/examples/runware-demo/esbuild/global.d.ts
@@ -0,0 +1,11 @@
+// These constants here are added by the base esbuild config
+
+declare const PLUGIN_VERSION: string;
+
+declare namespace NodeJS {
+ interface ProcessEnv {
+ CESDK_LICENSE: string;
+ RUNWARE_PROXY_URL: string;
+ ANTHROPIC_PROXY_URL: string;
+ }
+}
diff --git a/examples/runware-demo/index.html b/examples/runware-demo/index.html
new file mode 100644
index 00000000..33ddd997
--- /dev/null
+++ b/examples/runware-demo/index.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+ CE.SDK with Runware
+
+
+
+
+
+
+
+
+
diff --git a/examples/runware-demo/package.json b/examples/runware-demo/package.json
new file mode 100644
index 00000000..bdf2e0f5
--- /dev/null
+++ b/examples/runware-demo/package.json
@@ -0,0 +1,66 @@
+{
+ "private": true,
+ "name": "@imgly/plugin-runware-demo-web",
+ "version": "0.0.0",
+ "description": "Runware Demo",
+ "keywords": ["CE.SDK", "plugin", "AI", "Runware"],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/imgly/plugins.git"
+ },
+ "license": "SEE LICENSE IN LICENSE.md",
+ "author": {
+ "name": "IMG.LY GmbH",
+ "email": "support@img.ly",
+ "url": "https://img.ly"
+ },
+ "bugs": {
+ "email": "support@img.ly"
+ },
+ "source": "./src/index.ts",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.mjs",
+ "types": "./dist/index.d.ts"
+ }
+ },
+ "homepage": "https://img.ly/products/creative-sdk",
+ "files": ["LICENSE.md", "README.md", "CHANGELOG.md", "dist/", "bin/"],
+ "scripts": {
+ "start": "npm run watch",
+ "clean": "pnpm exec rimraf dist",
+ "purge": "pnpm exec rimraf node_modules",
+ "build": "pnpm run clean && pnpm exec node scripts/build.mjs",
+ "test": "echo No tests",
+ "dev": "node scripts/watch.mjs",
+ "dev:wait": "pnpm exec wait-on ./dist/index.mjs ./dist/index.d.ts --window 250 --timeout 60000",
+ "dev:types": "tsc --emitDeclarationOnly --watch --preserveWatchOutput",
+ "publish:latest": "pnpm run build && npm publish --tag latest --access public",
+ "publish:next": "pnpm run build && npm publish --tag next --access public",
+ "check:all": "concurrently -n type,pretty \"pnpm run check:types\" \"pnpm run check:pretty\"",
+ "check:pretty": "prettier --list-different './src/**/*.{ts,tsx}'",
+ "check:types": "tsc --noEmit",
+ "types:create": "tsc --emitDeclarationOnly"
+ },
+ "devDependencies": {
+ "@types/ndarray": "^1.0.14",
+ "chalk": "^5.3.0",
+ "concurrently": "^8.2.2",
+ "esbuild": "^0.19.11",
+ "eslint": "^8.51.0",
+ "lodash-es": "^4.17.21",
+ "typescript": "^5.3.3"
+ },
+ "dependencies": {
+ "@cesdk/cesdk-js": "^1.66.1",
+ "@cesdk/engine": "^1.66.1",
+ "@imgly/plugin-ai-apps-web": "^0.2.15",
+ "@imgly/plugin-ai-generation-web": "^0.2.15",
+ "@imgly/plugin-ai-image-generation-web": "^0.2.15",
+ "@imgly/plugin-ai-text-generation-web": "^0.2.15",
+ "@imgly/plugin-ai-video-generation-web": "^0.2.15",
+ "dotenv": "^16.5.0"
+ }
+}
diff --git a/examples/runware-demo/scripts/build.mjs b/examples/runware-demo/scripts/build.mjs
new file mode 100644
index 00000000..b8e529d8
--- /dev/null
+++ b/examples/runware-demo/scripts/build.mjs
@@ -0,0 +1,4 @@
+import * as esbuild from 'esbuild';
+import config from '../esbuild/config.mjs';
+
+await esbuild.build(config({ isDevelopment: false }));
diff --git a/examples/runware-demo/scripts/watch.mjs b/examples/runware-demo/scripts/watch.mjs
new file mode 100644
index 00000000..9deaeca2
--- /dev/null
+++ b/examples/runware-demo/scripts/watch.mjs
@@ -0,0 +1,12 @@
+import * as esbuild from 'esbuild';
+import config from '../esbuild/config.mjs';
+
+const context = await esbuild.context(config({ isDevelopment: true }));
+await context.watch();
+
+const { port } = await context.serve({
+ servedir: '.',
+ port: 5180
+});
+
+console.log(`\nš Runware Demo running at: http://localhost:${port}\n`);
diff --git a/examples/runware-demo/src/index.ts b/examples/runware-demo/src/index.ts
new file mode 100644
index 00000000..21e597b2
--- /dev/null
+++ b/examples/runware-demo/src/index.ts
@@ -0,0 +1,205 @@
+import CreativeEditorSDK from '@cesdk/cesdk-js';
+import AiApps from '@imgly/plugin-ai-apps-web';
+import RunwareImage from '@imgly/plugin-ai-image-generation-web/runware';
+import RunwareVideo from '@imgly/plugin-ai-video-generation-web/runware';
+import Anthropic from '@imgly/plugin-ai-text-generation-web/anthropic';
+import { Middleware } from '@imgly/plugin-ai-generation-web';
+
+function initialize(
+ selector: string,
+ options?: {
+ archiveUrl?: string;
+ license?: string;
+ }
+) {
+ document.addEventListener('DOMContentLoaded', function () {
+ const domElement = document.querySelector(selector);
+ if (domElement != null) {
+ CreativeEditorSDK.create(domElement, {
+ license: options?.license ?? process.env.CESDK_LICENSE ?? '',
+ userId: 'plugins-vercel',
+ callbacks: {
+ onUpload: 'local',
+ onExport: 'download',
+ onLoadArchive: 'uploadArchive'
+ },
+ featureFlags: {
+ archiveSceneEnabled: true,
+ dangerouslyDisableVideoSupportCheck: false
+ },
+ ui: {
+ elements: {
+ navigation: {
+ action: {
+ load: true,
+ export: true
+ }
+ }
+ }
+ }
+ }).then(async (instance) => {
+ // @ts-ignore
+ window.cesdk = instance;
+
+ await Promise.all([
+ instance.addDefaultAssetSources(),
+ instance.addDemoAssetSources({ sceneMode: 'Design' })
+ ]);
+
+ instance.ui.setDockOrder([
+ 'ly.img.ai.apps.dock',
+ ...instance.ui.getDockOrder().filter(({ key }) => {
+ return key !== 'ly.img.video.template' && key !== 'ly.img.template';
+ }),
+ 'ly.img.spacer',
+ 'byok.dock'
+ ]);
+
+ instance.ui.setCanvasMenuOrder([
+ {
+ id: 'ly.img.ai.text.canvasMenu'
+ },
+ {
+ id: 'ly.img.ai.image.canvasMenu'
+ },
+ {
+ id: 'ly.img.separator'
+ },
+ ...instance.ui.getCanvasMenuOrder()
+ ]);
+
+ instance.feature.enable('ly.img.preview', false);
+ instance.feature.enable('ly.img.placeholder', false);
+
+ await instance.engine.scene.loadFromArchiveURL(
+ options?.archiveUrl ??
+ 'https://img.ly/showcases/cesdk/cases/ai-editor/ai_editor_design.archive'
+ );
+ const [page] = instance.engine.scene.getPages();
+ instance.engine.scene.enableZoomAutoFit(page, 'Both');
+
+ instance.i18n.setTranslations({
+ en: {}
+ });
+
+ const errorMiddleware: Middleware = async (
+ input,
+ options,
+ next
+ ) => {
+ return next(input, options).catch((error) => {
+ console.error('Error:', error);
+ if (error.name === 'AbortError') {
+ // Ignore abort errors
+ return;
+ }
+ instance.ui.showDialog({
+ type: 'warning',
+ size: 'large',
+ content:
+ 'Due to high demand, we are currently unable to process your request. Please try again shortly - we appreciate your patience!'
+ });
+ // Throw abort error to stop the generation without further
+ // error notification.
+ throw new DOMException(
+ 'Operation aborted: Rate limit exceeded',
+ 'AbortError'
+ );
+ });
+ };
+
+ instance.engine.scene.setDesignUnit('Pixel');
+ instance.addPlugin(
+ AiApps({
+ debug: true,
+ dryRun: false,
+ providers: {
+ text2text: Anthropic.AnthropicProvider({
+ middleware: [errorMiddleware],
+ proxyUrl: process.env.ANTHROPIC_PROXY_URL
+ }),
+ text2image: [
+ RunwareImage.NanoBanana2Pro.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Pro.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Dev.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.GptImage1.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Seedream45.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Flex.Text2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ })
+ ],
+ image2image: [
+ RunwareImage.NanoBanana2Pro.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Pro.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Dev.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.GptImage1.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Seedream45.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareImage.Flux2Flex.Image2Image({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ })
+ ],
+ text2video: [
+ RunwareVideo.Sora2.Text2Video({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareVideo.Veo31.Text2Video({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ })
+ ],
+ image2video: [
+ RunwareVideo.Sora2.Image2Video({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareVideo.Sora2Pro.Image2Video({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ }),
+ RunwareVideo.Veo31.Image2Video({
+ middlewares: [errorMiddleware],
+ proxyUrl: process.env.RUNWARE_PROXY_URL
+ })
+ ]
+ }
+ })
+ );
+ });
+ }
+ });
+}
+
+export default initialize;
diff --git a/examples/runware-demo/tsconfig.json b/examples/runware-demo/tsconfig.json
new file mode 100644
index 00000000..ea17f997
--- /dev/null
+++ b/examples/runware-demo/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "target": "es2017",
+ "module": "es2020",
+ "lib": ["es2018", "dom"],
+ "moduleResolution": "bundler",
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "declaration": true,
+ "declarationDir": "dist/",
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ },
+ "include": ["src/**/*", "esbuild/global.d.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 78fd930d..5031609c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -296,6 +296,55 @@ importers:
specifier: ^5.3.3
version: 5.9.2
+ examples/runware-demo:
+ dependencies:
+ '@cesdk/cesdk-js':
+ specifier: 1.66.1
+ version: 1.66.1(react@18.3.1)
+ '@cesdk/engine':
+ specifier: 1.66.1
+ version: 1.66.1(react@18.3.1)
+ '@imgly/plugin-ai-apps-web':
+ specifier: ^0.2.15
+ version: 0.2.15(@cesdk/cesdk-js@1.66.1(react@18.3.1))
+ '@imgly/plugin-ai-generation-web':
+ specifier: ^0.2.15
+ version: 0.2.15(@cesdk/cesdk-js@1.66.1(react@18.3.1))
+ '@imgly/plugin-ai-image-generation-web':
+ specifier: ^0.2.15
+ version: 0.2.15(@cesdk/cesdk-js@1.66.1(react@18.3.1))
+ '@imgly/plugin-ai-text-generation-web':
+ specifier: ^0.2.15
+ version: 0.2.15(@cesdk/cesdk-js@1.66.1(react@18.3.1))
+ '@imgly/plugin-ai-video-generation-web':
+ specifier: ^0.2.15
+ version: 0.2.15(@cesdk/cesdk-js@1.66.1(react@18.3.1))
+ dotenv:
+ specifier: ^16.5.0
+ version: 16.6.1
+ devDependencies:
+ '@types/ndarray':
+ specifier: ^1.0.14
+ version: 1.0.14
+ chalk:
+ specifier: ^5.3.0
+ version: 5.6.0
+ concurrently:
+ specifier: ^8.2.2
+ version: 8.2.2
+ esbuild:
+ specifier: ^0.19.11
+ version: 0.19.12
+ eslint:
+ specifier: ^8.51.0
+ version: 8.57.1
+ lodash-es:
+ specifier: ^4.17.21
+ version: 4.17.21
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.2
+
examples/web:
dependencies:
'@cesdk/cesdk-js':