From aeeae2015c6f2a34ce05c97a47e731a164ba582e Mon Sep 17 00:00:00 2001 From: Julian Bilcke Date: Wed, 14 Aug 2024 14:38:30 +0200 Subject: [PATCH] neutral theme, comfy auth, dynamic workflows --- documentation/design/README.md | 70 ++++++ package-lock.json | 47 ++-- package.json | 11 +- .../providers/comfy-replicate/index.ts | 67 ++++-- .../providers/comfyui/getComfyWorkflow.ts | 33 --- .../api/resolve/providers/comfyui/index.ts | 50 +++- .../providers/comfyui/temporary_demo.json | 80 ------ src/app/embed/embed.tsx | 2 +- src/app/main.tsx | 27 ++- .../dialogs/iframe-warning/index.tsx | 4 +- src/components/dialogs/loader/index.tsx | 2 +- src/components/forms/FormSection.tsx | 2 +- src/components/mobile/MobileMenu.tsx | 25 ++ src/components/monitor/StaticPlayer/index.tsx | 4 +- src/components/settings/index.tsx | 6 +- src/components/settings/provider.tsx | 28 ++- src/components/tags/colors.ts | 2 +- .../toolbars/bottom-bar/metrics/index.tsx | 4 +- .../editors-menu/EditorsSideMenuItem.tsx | 4 +- src/components/toolbars/system-menu/index.tsx | 23 +- .../toolbars/top-menu/ToggleView/index.tsx | 2 +- src/components/toolbars/top-menu/index.tsx | 4 +- src/components/ui/alert.tsx | 5 +- src/components/ui/avatar.tsx | 2 +- src/components/ui/badge.tsx | 10 +- src/components/ui/button.tsx | 14 +- src/components/ui/card.tsx | 4 +- src/components/ui/checkbox.tsx | 2 +- src/components/ui/command.tsx | 14 +- src/components/ui/dialog.tsx | 8 +- src/components/ui/dropdown-menu.tsx | 17 +- src/components/ui/popover.tsx | 2 +- src/components/ui/progress.tsx | 4 +- src/components/ui/scroll-area.tsx | 2 +- src/components/ui/select.tsx | 11 +- src/components/ui/separator.tsx | 2 +- src/components/ui/sheet.tsx | 143 +++++++++++ src/components/ui/sonner.tsx | 8 +- src/components/ui/table.tsx | 11 +- src/components/ui/tabs.tsx | 6 +- src/components/ui/textarea.tsx | 2 +- src/components/ui/toast.tsx | 10 +- src/components/ui/tooltip.tsx | 2 +- src/components/ui/vertical-slider.tsx | 6 +- src/components/wizards/project/index.tsx | 6 +- src/experiments/moodboard/MoodboardView.tsx | 2 +- src/experiments/moodboard/Node.tsx | 2 +- src/lib/core/constants.ts | 2 +- src/services/debug.ts | 11 +- .../editors/script-editor/useScriptEditor.ts | 5 +- .../getDefaultWorkflowEditorState.ts | 4 +- .../workflow-editor/useDynamicWorkflows.ts | 34 +++ .../workflow-editor/useWorkflowEditor.ts | 15 +- .../workflows/comfyui/getComfyWorkflow.ts | 19 ++ .../workflows/comfyui/index.ts | 123 ++++++++-- .../workflow-editor/workflows/common/types.ts | 3 + .../workflow-editor/workflows/index.ts | 9 +- .../settings/getDefaultSettingsState.ts | 2 + src/services/settings/useSettings.ts | 227 +++--------------- 59 files changed, 760 insertions(+), 486 deletions(-) create mode 100644 documentation/design/README.md delete mode 100644 src/app/api/resolve/providers/comfyui/getComfyWorkflow.ts delete mode 100644 src/app/api/resolve/providers/comfyui/temporary_demo.json create mode 100644 src/components/mobile/MobileMenu.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/services/editors/workflow-editor/useDynamicWorkflows.ts create mode 100644 src/services/editors/workflow-editor/workflows/comfyui/getComfyWorkflow.ts create mode 100644 src/services/editors/workflow-editor/workflows/common/types.ts diff --git a/documentation/design/README.md b/documentation/design/README.md new file mode 100644 index 00000000..cfad874c --- /dev/null +++ b/documentation/design/README.md @@ -0,0 +1,70 @@ +# Design + +(those are just quick notes for now, we can elaborate later) + +## Designing Clapper for the Web + +### Targeted devices + +Currently Clapper works best on a laptop, +since the UI has been developed around the presence of a relatively wide screen, +a mouse and a touch pad (for horizontal scrolling in the timeline). + +However I think we should try to better support the following environments: + +- Tiny laptop (12", 11") +- Tablet (without a mouse) +- Desktop computer with multiple screens + +(mobile has its own chapter, see below in this document) + +### Use Web APIs in priority + +We should try to use standard Web APIs (ratified by the W3C) as much as possible, with polyfills in case some browsers don't implement them yet. + +We can use experimental standards (eg. WebGPU), +but since they are unstable and not supported by all browsers, they should not be mandatory to the experience. + +## Designing Clapper for desktop + +The experience on Desktop should be similar to the one in a browser, but we can do some changes: + +### Customizing the look of the app window + +Electron offers us to hide or customize the app's window, so we should do it. + +We don't have to follow the design principles of the underlying operating system (some apps don't care eg. video games, Spotify, Slack, FLStudio, Discord..) but it may be necessary for some operations (file pickers, installer, window management etc). + +### Use the native file system + +The big benefit of running Clapper as a desktop application is that we can access the file system, meaning we can work with files of arbitrary file length (note: for this kind of file system manipulation, we will have to use extra code eg. NodeJS) + +This can also be used for performance optimization such as using temporary files, or pre-computing things and store them in a cache for the next time Clapper is opened. + +### Download additional data for local use + +By running Clapper on the user's device, we can also make it download data of arbitrary size. + +This can help with various use cases, such as running AI models locally. + +For instance the desktop app LMStudio can download models from Hugging Face. + +### Call external or embedded native libraries + +We have complete freedom to ship Clapper with embedded native tools eg. a database or a native library (eg. a C++ library to run a LLM locally). + +We could even use Python scripts with Clapper. + +Please note however that we will have to make sure anything we embed works on various operating systems (Windows/macOS/Linux) so this requires dedicated skills and maintenance. + +### Use System Notifications + +When a job is pending, finished, a software update is ready etc. + +## Clapper Design For Mobile + +Currently Clapper can *run* on mobile, but it is not designed for it. + +So things like the top menu etc will be pretty much unuseable, unless we adapt them. + + diff --git a/package-lock.json b/package-lock.json index 7d139a5c..ff73b9cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@aitube/broadway": "0.2.3", "@aitube/clap": "0.2.3", - "@aitube/clapper-services": "0.2.3-1", + "@aitube/clapper-services": "0.2.3-2", "@aitube/client": "0.2.3", "@aitube/engine": "0.2.3", "@aitube/timeline": "0.2.3", @@ -33,7 +33,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", - "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", @@ -66,7 +66,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^0.2.1", - "comfydeploy": "^0.0.19-beta.13", + "comfydeploy": "^0.0.21", "date-fns": "^3.6.0", "dotenv": "^16.4.5", "fflate": "^0.8.2", @@ -89,6 +89,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", + "react-error-boundary": "^4.0.13", "react-hook-consent": "^3.5.3", "react-hotkeys-hook": "^4.5.0", "react-icons": "^5.2.1", @@ -99,7 +100,7 @@ "replicate": "^0.32.0", "sharp": "0.33.4", "sonner": "^1.5.0", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "three": "^0.164.1", "ts-node": "^10.9.2", @@ -109,7 +110,7 @@ "web-audio-beat-detector": "^8.2.12", "yaml": "^2.4.5", "zustand": "4.5.2", - "zx": "^8.1.3" + "zx": "^8.1.4" }, "devDependencies": { "@electron-forge/cli": "^7.4.0", @@ -191,9 +192,9 @@ } }, "node_modules/@aitube/clapper-services": { - "version": "0.2.3-1", - "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.2.3-1.tgz", - "integrity": "sha512-R6n9Wlte7E7bRIKh3Jf8xSZkvly2BgQ4p12OJY1rBNzgvDaUfHyFcCdCwKcBXzcDlWzGW5fjQIA7+OPeoXpC0Q==", + "version": "0.2.3-2", + "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.2.3-2.tgz", + "integrity": "sha512-Qd6Riridk4FVcTjlscxw5wsbUgojwi1wkTIjlgPluhT5J5kLyEJQL/hmT2gBDBRsB4kyieVNZiGflgXnauDENw==", "peerDependencies": { "@aitube/clap": "0.2.3", "@aitube/timeline": "0.2.3", @@ -9394,11 +9395,14 @@ } }, "node_modules/comfydeploy": { - "version": "0.0.19-beta.13", - "resolved": "https://registry.npmjs.org/comfydeploy/-/comfydeploy-0.0.19-beta.13.tgz", - "integrity": "sha512-m3sn8XCYJ9FjEhB6CoA8IYQ67CvizQuZ9vodNroiZB5BA2TakkGTK5TUj+PxmpOopdUZVhlSOVqjcB1+KQYJBQ==", - "peerDependencies": { - "zod": ">= 3" + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/comfydeploy/-/comfydeploy-0.0.21.tgz", + "integrity": "sha512-v5i6igitVWN6jmDlg35g8XyJuGsToD1sbhEZIz4nM3F6gSXZf2YjJzL/wzabsHlOtHhKsO/vdQAXuvO7/w8deQ==", + "dependencies": { + "zod": "^3.22.4" + }, + "engines": { + "node": ">=16" } }, "node_modules/comma-separated-tokens": { @@ -17745,6 +17749,17 @@ "react-dom": "^18.0.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz", + "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-hook-consent": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/react-hook-consent/-/react-hook-consent-3.5.3.tgz", @@ -19595,9 +19610,9 @@ "dev": true }, "node_modules/tailwind-merge": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", - "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz", + "integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/package.json b/package.json index d74f839f..195b3627 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "dependencies": { "@aitube/broadway": "0.2.3", "@aitube/clap": "0.2.3", - "@aitube/clapper-services": "0.2.3-1", + "@aitube/clapper-services": "0.2.3-2", "@aitube/client": "0.2.3", "@aitube/engine": "0.2.3", "@aitube/timeline": "0.2.3", @@ -60,7 +60,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", - "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", @@ -93,7 +93,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^0.2.1", - "comfydeploy": "^0.0.19-beta.13", + "comfydeploy": "^0.0.21", "date-fns": "^3.6.0", "dotenv": "^16.4.5", "fflate": "^0.8.2", @@ -116,6 +116,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", + "react-error-boundary": "^4.0.13", "react-hook-consent": "^3.5.3", "react-hotkeys-hook": "^4.5.0", "react-icons": "^5.2.1", @@ -126,7 +127,7 @@ "replicate": "^0.32.0", "sharp": "0.33.4", "sonner": "^1.5.0", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "three": "^0.164.1", "ts-node": "^10.9.2", @@ -136,7 +137,7 @@ "web-audio-beat-detector": "^8.2.12", "yaml": "^2.4.5", "zustand": "4.5.2", - "zx": "^8.1.3" + "zx": "^8.1.4" }, "devDependencies": { "@electron-forge/cli": "^7.4.0", diff --git a/src/app/api/resolve/providers/comfy-replicate/index.ts b/src/app/api/resolve/providers/comfy-replicate/index.ts index 49fe55b9..d291a7ad 100644 --- a/src/app/api/resolve/providers/comfy-replicate/index.ts +++ b/src/app/api/resolve/providers/comfy-replicate/index.ts @@ -1,7 +1,11 @@ -import { ClapSegmentStatus, getClapAssetSourceType } from '@aitube/clap' +import { + ClapSegmentCategory, + ClapSegmentStatus, + getClapAssetSourceType, +} from '@aitube/clap' import { ResolveRequest } from '@aitube/clapper-services' -import { getComfyWorkflow } from '../comfyui/getComfyWorkflow' +import { getComfyWorkflow } from '../../../../../services/editors/workflow-editor/workflows/comfyui/getComfyWorkflow' import { runWorkflow } from './runWorkflow' import { TimelineSegment } from '@aitube/timeline' @@ -11,22 +15,55 @@ export async function resolveSegment( if (!request.settings.replicateApiKey) { throw new Error(`Missing API key for "Replicate.com"`) } - const workflow = getComfyWorkflow(request) const segment: TimelineSegment = request.segment - try { - segment.assetUrl = await runWorkflow({ - apiKey: request.settings.replicateApiKey, - workflow, - }) - segment.assetSourceType = getClapAssetSourceType(segment.assetUrl) - } catch (err) { - console.error(`failed to call Replicate: `, err) - segment.assetUrl = '' - segment.assetSourceType = getClapAssetSourceType(segment.assetUrl) - segment.status = ClapSegmentStatus.TO_GENERATE - // request.segment.status = ClapSegmentStatus.ERROR + if (request.segment.category === ClapSegmentCategory.STORYBOARD) { + const inputFields = + request.settings.imageGenerationWorkflow.inputFields || [] + + // since this is a random "wild" workflow, it is possible + // that the field name is a bit different + // we try to look into the workflow input fields + // to find the best match + const promptFields = [ + inputFields.find((f) => f.id === 'prompt'), // exactMatch, + inputFields.find((f) => f.id.includes('prompt')), // similarName, + inputFields.find((f) => f.type === 'string'), // similarType + ].filter((x) => typeof x !== 'undefined') + + const promptField = promptFields[0] + if (!promptField) { + throw new Error( + `this workflow doesn't seem to have a parameter called "prompt"` + ) + } + + console.log(`TODO: inject parameters into the final request object`) + const workflow = `{}` // <- the final workflow sent to Replicate + + try { + segment.assetUrl = await runWorkflow({ + apiKey: request.settings.replicateApiKey, + workflow, + }) + segment.assetSourceType = getClapAssetSourceType(segment.assetUrl) + } catch (err) { + console.error(`failed to call Replicate: `, err) + segment.assetUrl = '' + segment.assetSourceType = getClapAssetSourceType(segment.assetUrl) + segment.status = ClapSegmentStatus.TO_GENERATE + // request.segment.status = ClapSegmentStatus.ERROR + } + } else if (request.segment.category === ClapSegmentCategory.VIDEO) { + // TODO do the same for video + throw new Error( + `Clapper doesn't support ${request.segment.category} generation for provider "Comfy.icu". Please open a pull request with (working code) to solve this!` + ) + } else { + throw new Error( + `Clapper doesn't support ${request.segment.category} generation for provider "Comfy.icu". Please open a pull request with (working code) to solve this!` + ) } return segment diff --git a/src/app/api/resolve/providers/comfyui/getComfyWorkflow.ts b/src/app/api/resolve/providers/comfyui/getComfyWorkflow.ts deleted file mode 100644 index 5bedd552..00000000 --- a/src/app/api/resolve/providers/comfyui/getComfyWorkflow.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ClapSegmentCategory } from '@aitube/clap' -import { getVideoPrompt } from '@aitube/engine' - -import { ComfyNode, ResolveRequest } from '@aitube/clapper-services' - -// TODO move this to @aitube/engine or @aitube/engine-comfy -export function getComfyWorkflow(request: ResolveRequest) { - let comfyWorkflow = '{}' - - if (request.segment.category === ClapSegmentCategory.STORYBOARD) { - comfyWorkflow = request.settings.comfyWorkflowForImage - } else if (request.segment.category === ClapSegmentCategory.VIDEO) { - comfyWorkflow = request.settings.comfyWorkflowForVideo - } - - // parse the node array from the ComfyUI workflow - const nodes = Object.values(JSON.parse(comfyWorkflow)) as ComfyNode[] - - const visualPrompt = getVideoPrompt(request.segments, request.entities) - - const output: Record = {} - - nodes.forEach((node, i) => { - if (typeof node.inputs.text === 'string') { - if (node._meta.title.includes('Prompt')) { - node.inputs.text = visualPrompt - } - } - output[`${i}`] = node - }) - - return JSON.stringify(output) -} diff --git a/src/app/api/resolve/providers/comfyui/index.ts b/src/app/api/resolve/providers/comfyui/index.ts index 7256d855..9b2c3d19 100644 --- a/src/app/api/resolve/providers/comfyui/index.ts +++ b/src/app/api/resolve/providers/comfyui/index.ts @@ -1,5 +1,6 @@ import { ResolveRequest } from '@aitube/clapper-services' import { + ClapAssetSource, ClapSegmentCategory, ClapSegmentStatus, generateSeed, @@ -7,6 +8,7 @@ import { } from '@aitube/clap' import { TimelineSegment } from '@aitube/timeline' import { + BasicCredentials, CallWrapper, ComfyApi, PromptBuilder, @@ -15,6 +17,7 @@ import { } from '@saintno/comfyui-sdk' import { getWorkflowInputValues } from '../getWorkflowInputValues' +import { decodeOutput } from '@/lib/utils/decodeOutput' export async function resolveSegment( request: ResolveRequest @@ -25,10 +28,25 @@ export async function resolveSegment( const segment: TimelineSegment = { ...request.segment } + const credentials: BasicCredentials = { + type: 'basic', + username: request.settings.comfyUiHttpAuthLogin, + password: request.settings.comfyUiHttpAuthPassword, + } + // for API doc please see: // https://github.com/tctien342/comfyui-sdk/blob/main/examples/example-t2i.ts const api = new ComfyApi( - request.settings.comfyUiApiUrl || 'http://localhost:8188' + request.settings.comfyUiApiUrl || 'http://localhost:8188', + request.settings.comfyUiClientId, + + // HTTP Auth is optional + // also in the future, we might support other things (bearer tokens?) + request.settings.comfyUiHttpAuthLogin + ? { + credentials, + } + : undefined ).init() if (request.segment.category === ClapSegmentCategory.STORYBOARD) { @@ -123,18 +141,38 @@ export async function resolveSegment( .onStart(() => console.log('Task is started')) .onPreview((blob) => console.log(blob)) .onFinished((data) => { - console.log( - data.images?.images.map((img: any) => api.getPathImage(img)) - ) + console.log('Pipeline finished') }) .onProgress((info) => console.log('Processing node', info.node, `${info.value}/${info.max}`) ) .onFailed((err) => console.log('Task is failed', err)) - const result = await pipeline.run() + const rawOutput = await pipeline.run() + + if (!rawOutput) { + throw new Error(`failed to run the pipeline (no output)`) + } + + const imagePaths = rawOutput.images?.images.map((img: any) => + api.getPathImage(img) + ) + + console.log(`imagePaths:`, imagePaths) + + const imagePath = imagePaths.at(0) + if (!imagePath) { + throw new Error(`failed to run the pipeline (no image)`) + } + + // TODO: check what the imagePath looks like exactly + const assetUrl = await decodeOutput(imagePath) + + console.log(`assetUrl:`, imagePaths) + segment.assetUrl = assetUrl + segment.assetSourceType = ClapAssetSource.DATA - console.log(`result:`, result) + // TODO: } else { throw new Error( `Clapper doesn't support ${request.segment.category} generation for provider "ComfyUI". Please open a pull request with (working code) to solve this!` diff --git a/src/app/api/resolve/providers/comfyui/temporary_demo.json b/src/app/api/resolve/providers/comfyui/temporary_demo.json deleted file mode 100644 index 589190ff..00000000 --- a/src/app/api/resolve/providers/comfyui/temporary_demo.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "3": { - "inputs": { - "seed": 303533494192794, - "steps": 20, - "cfg": 8, - "sampler_name": "euler", - "scheduler": "normal", - "denoise": 1, - "model": ["4", 0], - "positive": ["6", 0], - "negative": ["7", 0], - "latent_image": ["5", 0] - }, - "class_type": "KSampler", - "_meta": { - "title": "KSampler" - } - }, - "4": { - "inputs": { - "ckpt_name": "SD15/counterfeitV30_v30.safetensors" - }, - "class_type": "CheckpointLoaderSimple", - "_meta": { - "title": "Load Checkpoint" - } - }, - "5": { - "inputs": { - "width": 512, - "height": 512, - "batch_size": 1 - }, - "class_type": "EmptyLatentImage", - "_meta": { - "title": "Empty Latent Image" - } - }, - "6": { - "inputs": { - "text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,", - "clip": ["4", 1] - }, - "class_type": "CLIPTextEncode", - "_meta": { - "title": "CLIP Text Encode (Prompt)" - } - }, - "7": { - "inputs": { - "text": "text, watermark", - "clip": ["4", 1] - }, - "class_type": "CLIPTextEncode", - "_meta": { - "title": "CLIP Text Encode (Prompt)" - } - }, - "8": { - "inputs": { - "samples": ["3", 0], - "vae": ["4", 2] - }, - "class_type": "VAEDecode", - "_meta": { - "title": "VAE Decode" - } - }, - "9": { - "inputs": { - "filename_prefix": "ComfyUI", - "images": ["8", 0] - }, - "class_type": "SaveImage", - "_meta": { - "title": "Save Image" - } - } -} diff --git a/src/app/embed/embed.tsx b/src/app/embed/embed.tsx index b32005d3..2a185eb6 100644 --- a/src/app/embed/embed.tsx +++ b/src/app/embed/embed.tsx @@ -23,7 +23,7 @@ export function Embed() {
diff --git a/src/app/main.tsx b/src/app/main.tsx index bafc941a..2e669623 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -6,6 +6,7 @@ import { useSearchParams } from 'next/navigation' import { DndProvider, useDrop } from 'react-dnd' import { HTML5Backend, NativeTypes } from 'react-dnd-html5-backend' import { UIWindowLayout } from '@aitube/clapper-services' +import { ErrorBoundary, FallbackProps } from 'react-error-boundary' import { Toaster } from '@/components/ui/sonner' import { cn } from '@/lib/utils' @@ -14,12 +15,10 @@ import { Monitor } from '@/components/monitor' import { SettingsDialog } from '@/components/settings' import { LoadingDialog } from '@/components/dialogs/loader/LoadingDialog' -import { useUI, useIO } from '@/services' import { TopBar } from '@/components/toolbars/top-bar' import { Timeline } from '@/components/core/timeline' import { ChatView } from '@/components/assistant/ChatView' import { Editors } from '@/components/editors/Editors' -import { useTheme } from '@/services/ui/useTheme' import { BottomToolbar } from '@/components/toolbars/bottom-bar' import { FruityDesktop, FruityWindow } from '@/components/windows' import { ScriptEditor } from '@/components/editors/ScriptEditor' @@ -27,7 +26,10 @@ import { SegmentEditor } from '@/components/editors/SegmentEditor' import { EntityEditor } from '@/components/editors/EntityEditor' import { WorkflowEditor } from '@/components/editors/WorkflowEditor' import { FilterEditor } from '@/components/editors/FilterEditor' + +import { useUI, useIO, useTheme } from '@/services' import { useRenderLoop } from '@/services/renderer' +import { useDynamicWorkflows } from '@/services/editors/workflow-editor/useDynamicWorkflows' type DroppableThing = { files: File[] } @@ -47,6 +49,10 @@ function MainContent() { // perform its routine even when the monitor component is hidden useRenderLoop() + // this has to be done at the root of the app, that way it can + // sync workflows even when the workflow component is hidden + useDynamicWorkflows() + const [{ isOver, canDrop }, connectFileDrop] = useDrop({ accept: [NativeTypes.FILE], drop: (item: DroppableThing): void => { @@ -229,7 +235,7 @@ function MainContent() { : 'pointer-events-none hidden', `fixed top-9 h-[calc(100vh-36px)] w-screen flex-row overflow-hidden`, `items-center justify-center`, - `bg-stone-950` + `bg-neutral-950` )} >
+

Something went wrong:

+
{error.message}
+
+ ) +} + export function Main() { return ( - + + + ) diff --git a/src/components/dialogs/iframe-warning/index.tsx b/src/components/dialogs/iframe-warning/index.tsx index 89668ca0..0c07b8cc 100644 --- a/src/components/dialogs/iframe-warning/index.tsx +++ b/src/components/dialogs/iframe-warning/index.tsx @@ -18,7 +18,7 @@ export function IframeWarning() { return (

diff --git a/src/components/dialogs/loader/index.tsx b/src/components/dialogs/loader/index.tsx index f455146a..b34f7841 100644 --- a/src/components/dialogs/loader/index.tsx +++ b/src/components/dialogs/loader/index.tsx @@ -27,7 +27,7 @@ export function DeprecatedLoader({ )} >

Loading.. diff --git a/src/components/forms/FormSection.tsx b/src/components/forms/FormSection.tsx index 0ac12306..359bc34a 100644 --- a/src/components/forms/FormSection.tsx +++ b/src/components/forms/FormSection.tsx @@ -15,7 +15,7 @@ export function FormSection({

diff --git a/src/components/mobile/MobileMenu.tsx b/src/components/mobile/MobileMenu.tsx new file mode 100644 index 00000000..de926383 --- /dev/null +++ b/src/components/mobile/MobileMenu.tsx @@ -0,0 +1,25 @@ +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet' + +export function MobileMenu() { + return ( + + Open + + + Clapper + + TODO JULIAN: we need a way to display the hierarchical menu on + mobile + + + + + ) +} diff --git a/src/components/monitor/StaticPlayer/index.tsx b/src/components/monitor/StaticPlayer/index.tsx index 7c93297c..01505eda 100644 --- a/src/components/monitor/StaticPlayer/index.tsx +++ b/src/components/monitor/StaticPlayer/index.tsx @@ -54,7 +54,7 @@ export function StaticPlayer( }, [setStaticVideoRef]) const placeholder = ( -
+
{error ? {error} : No video yet}
) @@ -85,7 +85,7 @@ export function StaticPlayer( // autoPlay // muted loop - className="h-full rounded-lg border border-stone-950 object-cover" + className="h-full rounded-lg border border-neutral-950 object-cover" style={{}} /> ) : ( diff --git a/src/components/settings/index.tsx b/src/components/settings/index.tsx index f68b66d8..a054d503 100644 --- a/src/components/settings/index.tsx +++ b/src/components/settings/index.tsx @@ -67,7 +67,7 @@ export function SettingsDialog() {
-
+
{panels[showSettings]}