Compare commits

...
Author SHA1 Message Date
bflatastic 727246b90f added z index to scene state 2025-07-21 14:48:03 +07:00
bflatastic 81f5797e2f removed download link from completed card 2025-07-21 13:28:45 +07:00
Brandon Thomas a80c69ab1e update download links (#732) 2025-07-17 22:05:56 -04:00
Brandon Thomasandbflatastic da859fc2f8 marketing updates for the website (#722)
This is currently a draft / work in progress.

---------

Co-authored-by: bflatastic <bombayapirat@gmail.com>
2025-07-17 10:49:14 -04:00
Brandon Thomas 5f832d5d17 fix thumbnails in gallery and jobs, fix veo2 args, add hunyuan args, etc. (#731) 2025-07-17 09:51:59 -04:00
Brandon Thomas 612e58483c add model type to tasks db (#730) 2025-07-16 21:49:46 -04:00
Brandon Thomas 11b07b5833 debugging, resizing improvements; cleanup + refactor (#729) 2025-07-16 19:19:30 -04:00
Kasisnu a3f3bd8b3f bump nx version 2025-07-16 20:48:44 +05:30
bflatastic a93e921b51 3d model upload fix, upload button, modal block propagation of keyboard events 2025-07-16 13:57:09 +07:00
Brandon Thomas 58d5a172c4 consolidate cloudflare handling code (#728) 2025-07-16 01:09:50 -04:00
Brandon Thomas 012b7a9567 artcraft task polling (#727) 2025-07-15 22:28:03 -04:00
BFlat 18f4411f2a 2D reset canvas button and persist prompt boxes (#726)
- 2D reset canvas button under the left side toolbar. on click prompts a
confirmation to clear the canvas.
- all prompt boxes persist prompts, aspect ratio, system prompt and
reference images for that session until refreshed.
2025-07-15 18:46:58 -04:00
Brandon Thomas 0ed4d58387 refactor job api types for later reuse in http client (#725) 2025-07-15 01:09:10 -04:00
Brandon Thomas 9782bd33bc enable text prompts for videos (#724) 2025-07-14 22:14:36 -04:00
Brandon Thomas 2c9be7c262 fix aspect ratio controls on image gen (first pass) (#723)
This doesn't support all aspect ratios for all models. We'll need to
come back and offer per-model options.
2025-07-14 21:51:32 -04:00
Brandon Thomas a1f38b3695 upgrade nx (seems to be blocking netflify) (#721)
- tested artcraft locally
- tested website build
2025-07-14 16:27:08 -04:00
Brandon Thomas d5566c3466 remove unnecessary javascript package files (#720)
plus ignore some secrets that keep my git status dirty
2025-07-14 16:01:16 -04:00
Brandon Thomas 27d2ae47b3 remove "secrets" (not ours) that Netlify is complaining about (#719)
I don't think these are secrets. They're literally in publicly used and
consumed HTML content.

I'll need to remove the old HTML scraping libraries as they're no longer
relevant.
2025-07-14 15:42:24 -04:00
Brandon Thomas 926076441b update artcraft download links (#718) 2025-07-14 15:38:39 -04:00
Brandon Thomas 64353f1be9 video thumbnails from fal (#717) 2025-07-14 02:24:38 -04:00
bflatastic a8bb7f2720 windows dev script sqlx offline 2025-07-14 09:51:27 +07:00
Brandon Thomas ec1257ccf6 fix sqlx sqlite build for dev and CI (#716) 2025-07-13 20:26:57 -04:00
Brandon Thomas 6038fa4fd3 rename and retitle macos build action, add additional branch (#715) 2025-07-13 19:07:33 -04:00
Brandon ThomasandKasisnu 8ae6867e05 Kasisnu's artcraft release changes (#713)
Co-authored-by: Kasisnu <5044120+kasisnu@users.noreply.github.com>
2025-07-13 18:55:32 -04:00
Brandon Thomas 9bc24ac216 introduce sqlite with Tauri; task tracking database (#712) 2025-07-13 18:48:47 -04:00
bflatastic 590c2fbf96 multiselect and line node resizing/moving fix 2025-07-11 22:32:09 +07:00
bflatastic 212283e8d2 node sizing fix 2025-07-11 18:42:47 +07:00
BFlat 40d22b8ad0 2d bug fixes (#711)
**fixes:**
- artcraft logo link removed
- buggy layering system fixed
- no more circle resize jumping issue
- undo stack doesn't break images anymore
- undo, redo, copy, paste, delete keybinds work on mac now
- white bar at bottom removed
2025-07-11 15:58:33 +07:00
BFlat 01dca1f7b8 fixed image resizing, overflow drawing, show transform controls outside canvas (#710)
- images added to the canvas are now centered and 85% filled on the
canvas
- images resize properly without any restriction
- selected objects now show transform controls when overflowing the
canvas outside
- can now draw freely with the brush and it doesn't stop when you
overflow the edges
- code clean up and unneccessary comments removal
2025-07-10 09:38:50 +07:00
Brandon Thomas bc51b1d22b cleanup sqlx derive macros (#709) 2025-07-09 19:38:19 -04:00
Brandon Thomas fed32ce1fd tokens + enums - make sqlx optional (#708) 2025-07-09 18:21:56 -04:00
bflatastic 138ec975de rearrange button 2025-07-09 22:12:58 +07:00
bflatastic 895159afbf added shape color change 2025-07-09 22:05:51 +07:00
Brandon Thomas d44bd5edd9 fix fal webhook video metadata 2025-07-09 10:54:14 -04:00
Brandon Thomas 5ad15c4a2c background removal command (work in progress) (#707) 2025-07-09 10:02:17 -04:00
bflatastic 7541aa97a5 shape as outline icons instead of solid 2025-07-09 20:46:05 +07:00
BFlat 443648fcf9 bg color button color picker default to white (#706) 2025-07-09 20:32:15 +07:00
BFlat 1d8ed0e7a4 draw shapes by dragging (#705)
- select a shape and drag to draw it
- hold shift to make it proportional
2025-07-09 15:53:28 +07:00
BFlat 8657edf2a1 fix trackpad zoom (#704) 2025-07-09 14:33:24 +07:00
Brandon Thomas 0c283491e4 gpt-image-1 fixes: context images, working on 2d/3d, working with sora/artcraft (#703) 2025-07-07 18:45:48 -04:00
BFlat fa495c65d1 2d bug fixes and improvements (#702)
**fixes:**
- moved canvas fit to screen button to inside prompt box
- canvas fit to screen logic works properly and always centered
- canvas doesnt overflow screen and annoyingly scroll anymore
- brush now has an active state and border and shows brush icon when not
active
- hidden eraser button for now since it doesn't work

TODO:
- missing features
2025-07-07 22:33:39 +07:00
Brandon Thomas e751374192 enqueue 3d using the new endpoint 2025-07-07 01:55:02 -04:00
Brandon Thomas fb3fef02a6 batch get shouldn't require public visibility + error verbosity 2025-07-07 00:36:06 -04:00
Brandon Thomas fff76f8693 handle changes to storyteller API and let tauri inform the frontend (#701) 2025-07-06 21:18:53 -04:00
Brandon Thomas 4f3d23dcc8 Fal + OpenAI gpt-image-1 + preference routing (#700) 2025-07-06 01:14:54 -04:00
Kasisnu b749aa5d6a feat: super basic windows tauri action 2025-07-03 12:50:25 +05:30
Brandon Thomas ef8ccf149f Update documentation 2025-07-01 20:06:05 -04:00
Brandon Thomas 324bfc0964 move api clients crate to top-level of workspace for visibility 2025-07-01 18:50:51 -04:00
BFlat a4ee0eec15 resizable gallery modal and minimum size (#698)
- bug fix for expanded modal conflicting with resized size (not going full screen)
- gallery modal now resizable with set minimum size
- set a minimum size for lightbox resize
2025-07-01 10:19:48 +07:00
Brandon Thomas d8b8b4a037 remove unused imports (#697) 2025-06-30 00:03:52 -04:00
Brandon Thomas a842df3774 fix api docs 2025-06-29 22:22:25 -04:00
Brandon Thomas 4b3df535b5 configure provider priority and dispatch based on it (#696) 2025-06-27 21:15:31 -04:00
ArEnScandCrossPr0duct 3d06f89477 Feature/aspect ratio (#682)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-26 14:09:52 -04:00
CrossPr0duct d007cb81d5 Fix Drag and Drop Image, I had thought this change was already in. 2025-06-25 16:40:41 -04:00
CrossPr0duct 221d8bb95a the two magic undocumented values that will actually have this work without a bunch of code. 2025-06-25 15:52:33 -04:00
Brandon Thomas 760639d071 video model plumbing (#693) 2025-06-25 14:59:11 -04:00
Brandon Thomas 4e68169faf cookie debugging 2025-06-24 22:28:48 -04:00
Brandon Thomas 8090cdb526 call api endpoints from frontend (#692) 2025-06-24 15:41:02 -04:00
bflatastic ada4c77093 modal closable with esc key (for floating panels that's draggable and background interactable) 2025-06-24 08:22:00 +07:00
bflatastic 7bb14e1b17 topbar minor padding fix 2025-06-24 08:11:25 +07:00
bflatastic f9c8343165 library asset downloaded toast 2025-06-24 08:06:30 +07:00
Tanish Manku 53eb46d2b6 Add 500ms of timeout on tab switching (#691) 2025-06-23 13:03:57 -07:00
bflatastic f5a3ff67b3 small updates to router priority pane 2025-06-23 22:04:55 +07:00
BFlat 6b262d9fcb model router priority drag and drop reordering and login fixes (#690)
Model router priority tab in settings:
- drag and drop to update
- function calls on order change

Login refactor:
- fixed login (opens modal on log out)
- fixes bug where login modal opens even before login state is checked
(annoying)
- uses zustand states now
2025-06-21 02:22:32 +07:00
ArEnScandCrossPr0duct 744df6bcb8 Fixed issue with image background removal and drag and drop. (#689)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-20 12:55:26 -04:00
bflatastic 9a5ab77d3c don't return profile dropdown 2025-06-20 23:07:18 +07:00
Brandon Thomas 3e42ccc5e5 add artcraft/storyteller host to app info for debugging (#688) 2025-06-20 08:36:47 -04:00
BFlat 81bf7d712c feature/pricing modal (#684)
- pricing modal with tiers: free, plus, pro and max
- set up and ready to integrate with stripe
- small bug fix with the library modal buttons being squished

- upgrade button in TopBar is commented out for now
2025-06-20 12:36:37 +07:00
ArEnScandCrossPr0duct e9fe8bdf0d Fix/login (#686)
Moved Login to top level.
Minor bug fix later., logout after login in and exiting, log out and the button
won't trigger the modal

---------

Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-20 01:07:18 -04:00
Brandon Thomas 627fd6beeb roll back Cargo.lock 2025-06-20 00:46:06 -04:00
Brandon Thomas 77788a74e9 fix imports 2025-06-19 21:30:22 -04:00
Brandon Thomas 691f27eb23 misc: refactor client, move endpoints, more models, local testing, etc. etc. (#683) 2025-06-19 20:41:46 -04:00
Brandon Thomas 9008e1f4f4 logging for fal webhook handler 2025-06-19 16:04:38 -04:00
ArEnScandCrossPr0duct c11894692c Background removal race condition fixed. (#681)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-19 13:22:25 -04:00
Kasisnu e7d8613d09 tweaks to the action 2025-06-19 17:01:19 +05:30
Kasisnu 55832bdfc2 basic tauri/macos action 2025-06-19 16:52:32 +05:30
Brandon Thomas 49f18d7616 remaining tauri -> backend -> fal endpoints (#679) 2025-06-19 06:43:44 -04:00
BFlat 10603f7c86 feature/lightbox drag (#677)
- draggable and resizable lightbox

Bug found: if lightbox is open, gallery modal cannot open the filter
dropdown until lightbox is closed.
2025-06-19 00:41:21 +07:00
5e8c524f84 Feature/login fix (#678)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
2025-06-18 10:59:43 -04:00
Brandon Thomas 05f0fe1216 fal api endpoints in api gateway (#676) 2025-06-17 01:18:36 -04:00
ArEnScandCrossPr0duct 1f44f7e929 Feature/fixes (#675)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-16 23:58:35 -04:00
BFlat c56cc8700c gif tooltips for 2d and 3d (#674) 2025-06-16 14:49:02 +07:00
Brandon Thomas caaff569a6 fal client - lots of api wrappers (#673) 2025-06-16 01:08:27 -04:00
BFlat 3b0ed7a8ba feature: create 3d model modal (#668)
- Create 3d model from image works now (accessed via wand button next to
the + asset button).
- Modal pops up, or if you dont have a FAL api set in settings it'll pop
up an action reminder to set it first.
- You can choose image from library (opens gallery select mode) or
upload an image from computer.
- Pressing create enqueues the image to 3d and closes the modal.
Spinning loading toast on the top right says Generating... until it
finishes.
- When done, currently it just pops up in the library and you can drag
it onto the scene yourself.

**Current bugs/missing features:**
1. No 3D object image thumbnail in gallery
2. If you open up the gallery and try add the newly generated 3d object
to the scene, it will be stuck at "loading" text because of a CORS error
(not sure why). Pressing the button to refresh the gallery before
dragging it in seems to fix that though.
2025-06-14 20:44:03 -04:00
Brandon Thomas ea1905cfcf tauri bg removal fix (#672) 2025-06-14 01:25:26 -04:00
ArEnScandCrossPr0duct d1f28877ca Feature/add background remove (#670)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-13 19:07:41 -04:00
BFlat 1b8d8cbd97 login error messages now show properly (#671) 2025-06-13 17:33:36 +07:00
bflatastic 8be5013260 re-enable login modal 2025-06-13 16:29:09 +07:00
bflatastic 3c9b81c3ea bug fix: 3d promptbox events 2025-06-12 22:35:27 +07:00
BFlat 9d1b7d22ee bug fix: ensure no keyboard events leak through to the canvas (#669) 2025-06-12 22:30:43 +07:00
ArEnScandCrossPr0duct b046f6ff2a Feature/Zlayer and opacity issues (#667)
This adds all the z-layer work and context menus back for composition.
Also fixes the alpha issues and removes menus that are not needed for
now.

---------

Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-11 13:43:29 -04:00
BFlat df0b98ec63 drag and drop gallery 3d objects to 3d scene feature (#666)
- can now drag and drop 3d models onto the editor scene from the gallery
modal
2025-06-10 12:48:44 +07:00
BFlat f214577af1 2D canvas gallery image drop feature (#665)
- now you can drag and drop onto the 2D canvas from the gallery modal
2025-06-10 12:24:28 +07:00
ArEnScandCrossPr0duct 3efe9db4ca Create Menus and Bug Fixes (#664)
Removed:
Save and Load Scene for later.
Added undo and redo as button.
Hide Generate Image
Added a menu ( still needs logic code )
Fixed Opacity on brush.

---------

Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
2025-06-09 17:36:16 -04:00
Brandon Thomas 8208206e5a missing 2025-06-09 12:20:21 -04:00
Tanish Manku 4f33e74e3a Make 3D state save and load on tab change (#661) 2025-06-06 22:50:47 -07:00
Brandon Thomas f75b210dcd end-to-end fal in artcraft backend (#663) 2025-06-06 21:37:06 -04:00
Brandon ThomasandMichael Chung 7a07f196d1 Fix Safari issue (#662)
Co-authored-by: Michael Chung <michaelchung@mac.home>
2025-06-06 20:26:30 -04:00
a609282d8a Feature/page draw (#659)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
Co-authored-by: BFlat <bombayapirat@gmail.com>
2025-06-06 12:03:09 -04:00
BFlat 0a32e06cd5 reusable model selector (#660)
- reusable model selector component
- uses zustand with separated states for each tab

Image model list:
- GPT Image 1 (GPT-4o)
- Flux Pro Ultra
- Recraft 3
- Flux.1 Kontext

Video model list:
- Kling 2.0
- Runway
2025-06-06 17:31:36 +07:00
Brandon Thomas f1563feb91 codegen db 2025-06-05 09:24:06 -04:00
Brandon Thomas cc73ffab5d mark fal jobs complete (#658) 2025-06-05 09:19:38 -04:00
Brandon Thomas d374d9339c codegen db 2025-06-05 00:20:34 -04:00
Brandon Thomas 53681eac3f hunyuan2 image-to-3d (#657) 2025-06-05 00:15:13 -04:00
Brandon Thomas f22369ea4a update artcraft website with download links 2025-06-04 22:01:22 -04:00
Brandon Thomas f7060c0278 classify cloudflare errors 2025-06-04 20:33:27 -04:00
Brandon Thomas 2af8fbd9e1 npm install verbose flags 2025-06-04 20:10:05 -04:00
BFlat a4eb74292f login and popup modals (#652)
- moved artcraft login to first step before sora login
- made sora login skippable
- added pop up to block generation if sora session is not found
2025-06-04 19:47:27 -04:00
Tanish Manku 78b8c56066 Make 2D, 3D, image, video conditionally render (#654) 2025-06-04 14:47:39 -07:00
Brandon Thomas 221aa347bf forgot to commit these 2025-06-04 09:31:34 -04:00
Brandon Thomas 14a27ee803 fal endpoints: kling video 1.6 (#656) 2025-06-04 09:27:26 -04:00
Brandon Thomas 36e35dab0b fal on the backend (#653) 2025-06-03 14:59:28 -04:00
Brandon Thomas cb868c7275 add migrations and materialized final schema 2025-06-02 18:00:04 +05:30
Brandon Thomas b756f81421 vendor in fal-rs so we can hack in webhook support (then upstream it) (#650) 2025-06-02 01:43:18 -04:00
Brandon Thomas 0a1ad3d814 less flux-y generated images as background 2025-06-01 19:13:09 -04:00
Brandon Thomas 9df8c981cd fix api docs 2025-06-01 16:16:50 -04:00
Brandon Thomas e7622318c4 deprecate old endpoints (don't remove yet) (#649) 2025-06-01 15:51:37 -04:00
Brandon Thomas 352c7517cc openart client (maybe we won't do this just yet) (#648) 2025-05-30 19:39:41 -04:00
bflatastic 010a2f043a Remove old unused SettingsModal component 2025-05-30 16:09:25 +07:00
bflatastic cbb32f8275 Fix vertical line color in AssetModal and SettingsModal 2025-05-30 16:08:45 +07:00
bflatastic 0aecc97ede Update TopBar component: correct label for video tab from "Prompt to Video" to "Image to Video" 2025-05-30 15:44:43 +07:00
bflatastic 72a1bb07e0 Update command in windows_frontend_dev.ps1 to use 'npx' for executing nx dev 2025-05-30 13:05:06 +07:00
Brandon Thomas 6a01e4bba0 we don't need to spam the logs with the challenge html 2025-05-30 00:22:01 -04:00
Brandon Thomas 90d741323c check cloudflare challenges 2025-05-30 00:03:48 -04:00
Brandon Thomas 066d1c8bfb emit version to logging, add old sora cookie name 2025-05-29 21:38:07 -04:00
Brandon Thomas 3536736f53 better logging of sora session issues 2025-05-29 20:42:33 -04:00
Brandon Thomas 76d1f65164 more better alerts (#647) 2025-05-29 05:28:16 -04:00
Brandon Thomas ab72551ee7 better events (#646) 2025-05-29 05:03:32 -04:00
Brandon Thomas 9cf87cda5b start phasing fields out from our OpenAI client. These are a liability
It's smarter to just dump the fields we don't care about. That is less
of a chance that we got things wrong or things become incongruent at
runtime in the field.
2025-05-28 20:26:07 -04:00
Brandon Thomas 5163f357af fix nullable type in sora api 2025-05-28 12:15:08 -04:00
BFlat 42dfa9263d artcraft site landing update (#645) 2025-05-28 15:14:35 +07:00
Brandon Thomas f4d9385668 Fix package.lock (#644) 2025-05-28 01:20:09 -04:00
Brandon Thomas 189d6a6df8 refactor response types 2025-05-27 23:37:34 -04:00
Brandon Thomas 89cdc8492f tabs refactor (#643) 2025-05-27 22:54:04 -04:00
BFlat 46d2944823 background gallery for image tab (#638)
- reduced image size
- a more performant css only animation
2025-05-27 22:25:27 -04:00
ArEnScandMichael Chung 216e091257 Fixing build issue with button-link. (#642)
Co-authored-by: Michael Chung <michaelchung@mac.home>
2025-05-27 19:27:24 -04:00
Brandon Thomas f8bfa3ab2b fix nx builds (#641) 2025-05-27 18:58:47 -04:00
Brandon Thomas 569622bcc0 make frontend/clean_modules.sh take the nuclear option 2025-05-27 16:52:12 -04:00
Brandon Thomas 7aad79d16b timestamp when build finishes 2025-05-27 15:14:45 -04:00
Brandon Thomas d70f8ac41e fix circular deps in nx (#639) 2025-05-27 14:25:57 -04:00
ArEnSc 7097144e19 Removals (#636)
Co-authored-by: CrossPr0duct <alicextensor@gmail.com>
Co-authored-by: Michael Chung <michaelchung@mac.home>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
Co-authored-by: Tanish Manku <tanish2k09.dev@gmail.com>
Good work every one =)
2025-05-27 12:00:35 -04:00
Brandon Thomas 709af560a5 enqueue gpt-4o images too 2025-05-26 23:46:05 -04:00
Brandon Thomas 0f1cf93252 add fal image generation end-to-end (recraft, etc.) (#637) 2025-05-26 21:33:21 -04:00
bflatastic 4a10581e9a fix drag and drop logic for 3D and 2D tabs in PageEditor component 2025-05-26 23:31:47 +07:00
bflatastic 447a70b890 differentiate tabs to drop amd set up place for drag and drop on 2d 2025-05-26 23:26:13 +07:00
Brandon Thomas f4e998d945 cleanup deps (#635) 2025-05-26 01:38:27 -04:00
Brandon Thomas a954859ac2 cleanup dead dependencies 2025-05-25 23:55:15 -04:00
Brandon Thomas 2d8e835401 standardize actix versions in root Cargo.toml 2025-05-25 23:47:12 -04:00
Brandon Thomas c23380af90 codegen db 2025-05-25 23:22:10 -04:00
Brandon Thomas c34f75d071 cleanup hakari packages 2025-05-25 23:15:32 -04:00
Brandon Thomas 8a886e9033 cleanup scripts (2) 2025-05-25 22:57:01 -04:00
Brandon Thomas 3101df8211 cleanup scripts 2025-05-25 22:53:40 -04:00
Brandon Thomas a4c264ebbf cleanup packages, fixup storyteller-web 2025-05-25 22:50:45 -04:00
Brandon Thomas 3d807fdeb2 refactor database crates 2025-05-25 22:33:18 -04:00
Brandon Thomas b3a62cce64 refactor packages 2025-05-25 22:09:59 -04:00
Brandon Thomas 42ccc25e4d fix video enqueue 2025-05-25 21:31:56 -04:00
Brandon Thomas 6595518b35 add detailed build info in an about settings pane (#634) 2025-05-25 14:45:50 -04:00
ArEnSc 618cbc8f0e Feature/clean (#633)
Clean up the TS work unify and the app
2025-05-24 23:31:25 -04:00
BFlat b31a152c1a text to image tab (commented out) (#632) 2025-05-24 10:57:28 +07:00
BFlat 0e2169b902 updated topbar menu icons with tooltip (#631)
- animated on hover
- to accommodate more pages
2025-05-23 23:20:09 +07:00
Michael Chung 0ee9b7c442 fix: remove DialogTitle from DialogEditText 2025-05-23 11:33:48 -04:00
bflatastic 17495276e7 Refactor GalleryModal refresh logic and add refresh button
- Simplified the refresh logic by extracting it into a separate `refreshGallery` function.
- Added a refresh button with a tooltip to allow users to manually refresh the gallery items.
- Updated the useEffect to call the new refresh function when the modal opens, ensuring the gallery is always up-to-date.
2025-05-23 20:47:29 +07:00
bflatastic 2d78fd5a6f library always refreshed on open 2025-05-23 20:39:40 +07:00
BFlat 98e513e917 gallery dnd code refactor (#630)
- cleaner code + reusable image drop function for later 2d image drop
implementation
2025-05-23 09:21:43 +07:00
ArEnScandMichael Chung 97eb1c65ca Fix cors allow all urls for now, since there isn't really a way to ha… (#628)
…ck the app lol.

Co-authored-by: Michael Chung <michaelchung@mac.home>
2025-05-22 18:43:59 -04:00
bflatastic ba82a438dd image not dropping at correct position fix 2025-05-23 02:11:54 +07:00
bflatastic de7b990ec5 bug fix: select mode library modal not closing on X click 2025-05-23 01:35:50 +07:00
BFlat 7b90c59675 library modal signals bug fix (#627)
- fixed weird behavior with library modal not opening
- image drag and drop more accurate position (production drag and drop
still doesnt work - looking into it)
2025-05-23 01:31:49 +07:00
Brandon Thomas 11f1a5f41a sora aspect ratios (#626) 2025-05-22 00:09:59 -04:00
BFlat b92cd58f81 Enhance GalleryModal UX with Filters, Drag-and-Drop, Pagination, and UI Improvements (#625)
**✨ Features & Enhancements:**
- GalleryModal Filters: Replaced tab-based navigation with a more
scalable filter system.
- Image Fit Toggle: Allows toggling image fit display mode for better
previewing.
- Pagination & Infinite Scroll: Improved item loading performance using
soft refresh and infinite scroll logic.
- Drag-and-drop setup for gallery items
- Prevented dragging for video items
- Added support for image drop in PageEditor (3D)
- "My Library" Label Updates
2025-05-21 23:29:06 +07:00
Brandon Thomas 914c26b4c6 classify gateway timeout 2025-05-21 11:04:32 -04:00
Brandon Thomas fe8939a0f6 incomplete runwayml implementation (pausing for now) (#624) 2025-05-20 23:39:34 -04:00
Tanish Manku c809ff88fa Add save/load support for character poses (#623) 2025-05-20 20:13:04 -04:00
bflatastic 86675568a8 Update modal components for improved drag functionality and styling
- Increased the height of the drag handle in AssetModal and GalleryModal for better usability.
- Adjusted z-index properties for elements within AssetModal to ensure proper layering.
- Enhanced the modal's drag behavior to allow overflow beyond viewport edges, improving user experience when repositioning.
2025-05-21 02:32:20 +07:00
bflatastic a8927cc78d Refactor expand button in modal component for improved styling and functionality
- Updated the ExpandButton component to accept size props for better responsiveness.
- Replaced SVG icons with FontAwesome icons for a more consistent design.
- Adjusted the GalleryModal layout to include a gap between buttons for enhanced usability.
2025-05-21 02:17:17 +07:00
bflatastic 0e8dd19ecf Enhance modal functionality with draggable support and expand button
- Added draggable functionality to the modal, allowing users to reposition it.
- Introduced an expand button to toggle full-screen mode for the modal.
- Updated modal backdrop to support interaction options and improved styling.
- Adjusted AssetModal and GalleryModal components to utilize new modal features.
2025-05-21 02:06:51 +07:00
bflatastic b32a2e79b9 promptbox height bug fix 2025-05-20 22:47:38 +07:00
bflatastic a662c5809d lightbox videos fix 2025-05-20 22:31:27 +07:00
Michael Chung 7498720674 Fixing the gallery modal to show videos and images. 2025-05-20 11:14:36 -04:00
ArEnScandMichael Chung c8a4490b52 Feature/add to scene video 2 (#621)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-20 09:22:46 -04:00
Brandon Thomas c4e30e6dc7 swap out the background (#620) 2025-05-19 23:01:33 -04:00
ArEnScandMichael Chung 12070f14ad Feature/add to scene video 2 (#619)
Adding videos to scene via signal.

---------

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-19 02:19:31 -04:00
BFlat 77a103de06 gallery videos tab (#617)
- 16/9 ratio grid items
- lightbox for videos will only show download button
2025-05-19 10:58:34 +07:00
BFlat 82f5805f24 image-to-video generation page with background gallery and UI components (#616) 2025-05-19 09:51:52 +07:00
f7c9ebfaa3 Feature/video addition (#615)
Co-authored-by: Michael Chung <michaelchung@Michaels-MacBook-Pro-2.local>
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-18 20:38:12 -04:00
Brandon Thomas 76cd00cc13 add fal hunyuan image-to-3d (#614) 2025-05-18 15:37:55 -04:00
Brandon Thomas cb64f45b48 refactor artcraft crates to organize by service (module reorg) (#613) 2025-05-18 00:53:00 -04:00
Brandon Thomas 6751ab0b60 fal video integration (#612) 2025-05-17 19:47:40 -04:00
ArEnScandMichael Chung 9827a3efc5 Feature/add scene (#611)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-15 22:09:38 -04:00
Brandon Thomas 93b53d84cd remove some dead code + fix missing "dev" assets (#610) 2025-05-15 18:32:03 -04:00
Brandon Thomas a7794f6338 don't need to build for editor2d anymore 2025-05-15 17:20:56 -04:00
ArEnScandMichael Chung ef03e292c3 Add scene to 2d Lightbox modal. (#609)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-14 12:11:19 -04:00
BFlat 0e8c1fa3e7 Update landing2 mobile videos fix 2025-05-14 12:07:07 +07:00
BFlat af08834de7 artcraft landing 2 (#608) 2025-05-14 02:28:05 +07:00
Brandon Thomas e728806c04 more csp fixes 2025-05-13 14:18:29 -04:00
Brandon Thomas 3e605e7b18 add google analytics to CSP 2025-05-13 14:03:31 -04:00
Brandon Thomas d244524abc fal api integration in tauri + frontend controls + background removal (#607) 2025-05-13 13:12:49 -04:00
bflatastic 8e455e27a9 fixed: Artcraft login in onboarding shown when already logged into Artcraft 2025-05-13 11:55:44 +07:00
Brandon Thomas e0df1644a5 switch order of 2d/3d 2025-05-12 19:11:29 -04:00
Brandon Thomas 736d033ebf status alerts on 2d side 2025-05-12 18:54:42 -04:00
Brandon Thomas af3f0baa63 handle sora logged out state after app starts 2025-05-12 18:44:54 -04:00
Brandon Thomas a438dd13dc add Google Analytics GA4 2025-05-12 18:10:23 -04:00
Tanish Manku f7b76a7abe Add manual mouse support in Safari and AppleWebkit (#606) 2025-05-12 15:02:56 -07:00
Tanish Manku 9435af3e2a Change the relative pathing from base config (#605) 2025-05-12 14:23:09 -07:00
Tanish Manku 77efda1d66 Make 2D and 3D mount at the same time (#604) 2025-05-12 14:15:02 -07:00
Brandon Thomas fdc27b6ccd surface non-onboarding; do not retry non-onboarded requests 2025-05-12 17:00:25 -04:00
Brandon Thomas e3abd500db Explicit CSP on Mac instead of wildcards; this works on production Mac but may not fix Windows (#603) 2025-05-12 15:32:49 -04:00
Brandon Thomas ee4de4126d timeouts for uploads / remix (#602) 2025-05-12 12:26:41 -04:00
Brandon Thomas 4810ed30ae cache sora stats 2025-05-11 04:41:51 -04:00
Brandon Thomas 0b6593a063 fix preferences 2025-05-11 04:00:25 -04:00
Brandon Thomas b0e2dce140 fix event listeners for toasts; extra sounds (#601) 2025-05-11 03:41:14 -04:00
Brandon Thomas c4f401923b various sora / generate improvements (#600) 2025-05-11 01:02:00 -04:00
Brandon Thomas 24559a3554 username error 2025-05-09 19:00:28 -04:00
ArEnScandMichael Chung 5a26aed728 The bug was due to forggeting to bind this to the snapShotOfCurrentFr… (#599)
fix the generation problem

---------

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-09 18:07:55 -04:00
Tanish Manku 95773c1c23 Point 3D tsconfig to the right subdirectory (#598) 2025-05-09 14:27:13 -07:00
Michael Chung 2d01073434 Remove tab selector 2025-05-09 13:56:00 -04:00
BFlat 1c62092d94 fix 3d resizing bug (#597) 2025-05-09 13:13:53 -04:00
bflatastic 5e22e17dfb popover width fix 2025-05-09 22:49:05 +07:00
bflatastic 611da775a9 bug fix: add image fix 2025-05-09 22:23:42 +07:00
bflatastic 6fc3f73579 artcraft block color update 2025-05-09 22:10:53 +07:00
BFlat 562cb99c51 2d editor fixes (#596)
- left toolbar now updates properly
- modals open properly
- styling fixes
- adjusted canvas center positioning to accommodate top header bar
2025-05-09 21:53:58 +07:00
bflatastic 67d280df84 remove old demo modal 2025-05-09 20:38:05 +07:00
8ccc8927a9 Feature/merge 2d 3d diverge (#593)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
Co-authored-by: Tanish Manku <tanish2k09.dev@gmail.com>
2025-05-08 23:46:30 -04:00
Brandon Thomas a21e7331cc persist window position to disk and restore it 2025-05-08 22:54:58 -04:00
Brandon Thomas 0f0f8eb39c window size heuristic to keep dev tools from resizing window 2025-05-08 22:32:02 -04:00
Brandon Thomas b64ed411b1 artcraft account login/logout (#595) 2025-05-08 21:33:38 -04:00
ArEnScandMichael Chung a155b419e1 Fix 3d editor menu buttons hide the scene saving and loading. (#594)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-08 18:53:04 -04:00
Brandon Thomas e6177005df never block the prompt box 2025-05-08 17:59:32 -04:00
Brandon Thomas 1c78e17565 events for enqueue success / fail + more sounds and toasts (#592) 2025-05-08 17:43:12 -04:00
Brandon Thomas 0769bfad28 sora logout handler 2025-05-08 14:00:14 -04:00
BFlat 183946a943 Bflat/settings pane update (#591) 2025-05-08 17:40:54 +07:00
BFlat d43ee0f6c9 login modal and settings panel update (#590)
- login now flows automatically smoothly to the next step after login
- settings modal: misc/audio panes styling and toggle switch
- account settings disconnect sora account button

remaining TODO: 
- link logout sora endpoint
2025-05-08 14:21:01 +07:00
Brandon Thomas eec59c218d add login success event 2025-05-08 01:07:33 -04:00
Brandon Thomas 5c1e72f8f4 rename package 2025-05-07 23:59:44 -04:00
Brandon Thomas d91243b1f5 success and failure toasts; less discord info spam 2025-05-07 23:47:30 -04:00
Brandon Thomas 83cecad280 build script for mac 2025-05-07 23:13:23 -04:00
Brandon Thomas 06313f54a5 update settings panes (#588) 2025-05-07 22:52:47 -04:00
Brandon Thomas ef3ca88054 sound playing restored 2025-05-07 22:02:38 -04:00
Brandon Thomas cfcf7ab5aa refactor events: centrally located in rust for better discoverability 2025-05-07 21:15:13 -04:00
Brandon Thomas 4cc215495a slower polling 2025-05-07 19:11:19 -04:00
Brandon Thomas f1d14ab73f better scripts for unix development 2025-05-07 17:59:49 -04:00
Brandon Thomas 5cb8f5b93a cloudflare 524 2025-05-07 17:35:38 -04:00
Brandon Thomas 9decf8bf88 fix windows builds 2025-05-07 15:35:28 -04:00
Brandon Thomas 63a7f99c53 fix windows build issue 2025-05-07 14:55:48 -04:00
Brandon Thomas 9dd16f2e80 windows production build script 2025-05-07 14:42:40 -04:00
Brandon Thomas abe2d2a093 Windows scripts for running dev mode 2025-05-07 14:31:07 -04:00
bflatastic 552d5553c0 uncommented login modal and added temp next button 2025-05-07 23:15:59 +07:00
BFlat 0d33358ead upload flow update (#587)
- changed and moved the main upload asset button to the left sidebar
above the categories for better visibility
- added individual upload buttons to all the category pages except
skybox - they open the same upload modal but sepcific to its category
only.
- flow update: once an upload succeeds the upload modal closes itself
and automatically reopens the asset modal to the mine tab and previously
uploaded category (so users don't have to have a hard time finding or
navigating to it)
2025-05-07 16:18:45 +07:00
Brandon Thomas 908ff43be3 fix sora / chatgpt login, enhance login flow (#586) 2025-05-07 03:33:53 -04:00
3698 changed files with 159811 additions and 39259 deletions
@@ -0,0 +1,232 @@
name: "Artcraft MacOS Publish"
on:
push:
branches:
- release
- artcraft-release
- artcraft-macos # Additional branch for MacOS testing
# This workflow creates Universal macOS builds with signing certificate support.
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
jobs:
publish-artcraft:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
# Universal macOS builds (arm64 and x86_64)
- platform: 'macos-latest-xlarge'
args: '--target universal-apple-darwin'
# Uncomment below for additional platforms
# - platform: 'ubuntu-22.04'
# args: ''
# - platform: 'windows-latest'
# args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest-xlarge' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: install frontend dependencies
run: |
# Navigate to the frontend directory to install dependencies
cd ./frontend
npm cache clean --force
echo "Installing all workspace dependencies..."
npm ci
echo "=== Version Information ==="
echo "Node version:"
node --version
echo "NPM version:"
npm --version
# Remove mismatched @nx packages and reinstall with correct versions
npm install --save-dev @nx/remix@21.1.1 @nx/js@21.1.1 @nx/react@21.1.1 @nx/vite@21.1.1 @nx/eslint@21.1.1
echo "=== Nx versions after adjustment ==="
npm list --depth=0 | grep nx
- name: import Apple Developer Certificate
if: matrix.platform == 'macos-latest-xlarge'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
echo "Starting certificate import process..."
# Create certificate file
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
# Debug: Check if certificate file exists and has content
echo "Certificate file info:"
ls -la certificate.p12
file certificate.p12
# Debug: Check certificate contents without password (just structure)
echo "Certificate structure (may show error if password protected):"
openssl pkcs12 -info -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -noout || echo "Could not read certificate structure"
# Debug: Try to list what's in the p12 file
echo "Attempting to list certificate contents:"
security cms -D -i certificate.p12 || echo "Not a CMS file, trying PKCS12..."
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -info -nodes 2>&1 | head -20 || echo "Failed to read PKCS12 contents"
# Debug: Extract certificate details to verify type and expiration
echo "Extracting certificate details..."
# Extract just the certificate portion and examine it
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)" || echo "Failed to parse certificate details"
# Also try to see what type of certificate this is
echo "Certificate type check:"
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(CN=|OU=)" || echo "Could not determine certificate type"
# Debug: Check if private key exists
echo "Checking for private key..."
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nocerts -nodes 2>&1 | grep -E "(PRIVATE KEY|RSA KEY)" | head -5 || echo "No private key found"
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
# Import certificate - ensure both certificate and private key are imported
echo "Importing certificate into keychain..."
# Import the p12 directly - this is the most reliable method
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
# Alternative: Try with additional flags if the first import didn't create an identity
echo "Verifying import..."
if ! security find-identity -v build.keychain | grep -q "1 valid identities found"; then
echo "First import didn't create identity, trying with -A flag..."
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -A
fi
# Set trust settings for the certificate
echo "Setting trust settings..."
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
# Unlock the keychain again to ensure it's accessible
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
# Add the keychain to the search list
security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed 's/^[[:space:]]*"//;s/"$//')
# List all certificates to debug
echo "Listing all keychains:"
security list-keychains
echo "Listing all certificates in build keychain:"
security find-certificate -a -p build.keychain || echo "No certificates found"
echo "Detailed certificate info:"
security find-certificate -c "Learning Machines" build.keychain || echo "Certificate 'Learning Machines' not found"
# Check certificate trust settings
echo "Checking certificate trust settings:"
security dump-trust-settings || echo "No custom trust settings"
# Try to find codesigning identities
echo "Finding all identities in keychain:"
security find-identity -v build.keychain || echo "No identities found"
echo "Finding codesigning identities:"
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
# Additional debug: Check if this is a valid Developer ID cert
echo "Checking for Developer ID certificates:"
security find-certificate -a build.keychain | grep -A 5 -B 5 "Developer ID" || echo "No Developer ID certificates found"
- name: verify certificate
if: matrix.platform == 'macos-latest-xlarge'
run: |
echo "=== Certificate Verification ==="
echo "1. Checking codesigning identities:"
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
echo -e "\n2. Checking all identities:"
security find-identity -v build.keychain || echo "No identities found"
echo -e "\n3. Checking certificates in keychain:"
security find-certificate -c "Learning Machines" build.keychain -p | openssl x509 -text -noout | grep -E "(Subject:|CN=)" || echo "Could not parse certificate"
# Set the correct signing identity
echo "APPLE_SIGNING_IDENTITY=Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" >> $GITHUB_ENV
echo "Proceeding with identity: Developer ID Application: Learning Machines LLC (DJ6XS33FX8)"
# Also set the team ID
echo "APPLE_DEVELOPMENT_TEAM=DJ6XS33FX8" >> $GITHUB_ENV
- name: Pre-build frontend to avoid Tauri build issues
run: |
# Build the frontend app from the correct directory
cd ./frontend
# Increase Node.js memory limit for the build
export NODE_OPTIONS="--max-old-space-size=8192"
# Use npx to ensure we use the local nx version
echo "Building with npx nx..."
npx nx run editor3d:build
echo "Checking build output..."
ls -la apps/editor3d/dist/ | head -10
# Update Tauri config to skip the build (since we already built)
cd ${{ github.workspace }}/crates/desktop/artcraft
cp tauri.conf.json tauri.conf.json.bak
# Set beforeBuildCommand to empty or a no-op command
if command -v jq &> /dev/null; then
jq '.build.beforeBuildCommand = ""' tauri.conf.json > tauri.conf.json.tmp
mv tauri.conf.json.tmp tauri.conf.json
else
node -e "const fs = require('fs'); const config = JSON.parse(fs.readFileSync('tauri.conf.json', 'utf8')); config.build.beforeBuildCommand = ''; fs.writeFileSync('tauri.conf.json', JSON.stringify(config, null, 2));"
fi
- name: build and publish
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_DEVELOPMENT_TEAM: ${{ env.APPLE_DEVELOPMENT_TEAM }}
# Use the SQLx query cache for SQLite
SQLX_OFFLINE: true
with:
projectPath: ./crates/desktop/artcraft
tagName: artcraft-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
releaseName: "Artcraft v__VERSION__"
releaseBody: "See the assets to download this version and install."
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
@@ -0,0 +1,96 @@
name: "Artcraft Windows Publish"
on:
push:
branches:
- release
- artcraft-release
- artcraft-windows # Additional branch for Windows testing
# This workflow creates Windows builds.
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
jobs:
publish-artcraft-windows:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
# Windows builds
- platform: 'windows-latest'
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: install frontend dependencies
run: |
# Navigate to the frontend directory to install dependencies
cd ./frontend
npm cache clean --force
echo "Installing all workspace dependencies..."
npm ci
echo "=== Version Information ==="
echo "Node version:"
node --version
echo "NPM version:"
npm --version
# Remove mismatched @nx packages and reinstall with correct versions
npm install --save-dev @nx/remix@21.1.1 @nx/js@21.1.1 @nx/react@21.1.1 @nx/vite@21.1.1 @nx/eslint@21.1.1
echo "=== Nx versions after adjustment ==="
npm list --depth=0 | findstr nx
- name: Pre-build frontend to avoid Tauri build issues
run: |
# Build the frontend app from the correct directory
cd ./frontend
# Increase Node.js memory limit for the build
$env:NODE_OPTIONS="--max-old-space-size=8192"
# Use npx to ensure we use the local nx version
echo "Building with npx nx..."
npx nx run editor3d:build
echo "Checking build output..."
dir apps/editor3d/dist/ | Select-Object -First 10
# Update Tauri config to skip the build (since we already built)
cd ${{ github.workspace }}/crates/desktop/artcraft
Copy-Item tauri.conf.json tauri.conf.json.bak
# Set beforeBuildCommand to empty using PowerShell
$config = Get-Content tauri.conf.json | ConvertFrom-Json
$config.build.beforeBuildCommand = ""
$config | ConvertTo-Json -Depth 10 | Set-Content tauri.conf.json
- name: build and publish
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Use the SQLx query cache for SQLite
SQLX_OFFLINE: true
with:
projectPath: ./crates/desktop/artcraft
tagName: artcraft-windows-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
releaseName: "Artcraft Windows v__VERSION__"
releaseBody: "Windows build - See the assets to download this version and install."
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
+4 -2
View File
@@ -24,11 +24,13 @@ twitch_secrets.toml
*-secrets.env
# Utilities with secrets
script/production_database_secret_backup.sh
script/production_database_secret_connect.sh
production_database_secret_connect.sh
production_database_secret_query.sh
production_database_voice_clone_report_download.sh
script/artcraft/mac_signing_build.sh
script/production_database_secret_backup.sh
script/production_database_secret_connect.sh
script/socat_dev_database_connect.sh
# Scratch data
scratch/
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n status = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 13
},
"nullable": []
},
"hash": "0fa824822cd94024dce21243f41e69e4a3e8685c4de676834a7c0ee14982c976"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO tasks (\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_subscriber_id,\n frontend_subscriber_payload\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 8
},
"nullable": []
},
"hash": "22b91a21cbb88abc9bc17b338cf2f9418e08e414667d44476623eb141ab17b87"
}
@@ -0,0 +1,113 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n \n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "status: enums::common::job_status_plus::JobStatusPlus",
"type_info": {
"type": "String",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
"char_set": 224,
"max_size": 76
}
},
{
"ordinal": 2,
"name": "external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "external_third_party_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 256
}
},
{
"ordinal": 4,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "creator_ip_address",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 160
}
},
{
"ordinal": 7,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 8,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
true,
true,
true,
true,
false,
false,
false
]
},
"hash": "64dfacc1f0552d539f009fea91e902a2fe296c06ad539808596bbf2c8fc9eaa0"
}
@@ -48,7 +48,7 @@
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
@@ -1,12 +1,12 @@
{
"db_name": "MySQL",
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n \n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 38
"Right": 39
},
"nullable": []
},
"hash": "c58f414b5e4d0f0420d1894e2f40311e5613919f49f3ac91f81f5fc2bef1bea8"
"hash": "b4470e607c005421dd8504b9f1d695080a581936ac6ce00b6f437b2764e741be"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n UPDATE tasks\n SET task_status = ?\n WHERE id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "dc0367e9e43c3d7dda3623e77337ab4529eac2192475be7d08589886703ba1cb"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE generic_inference_jobs\nSET\n status = ?,\n on_success_result_entity_type = ?,\n on_success_result_entity_token = ?,\n failure_reason = NULL,\n internal_debugging_failure_reason = NULL,\n success_execution_millis = NULL,\n success_inference_execution_millis = NULL,\n retry_at = NULL,\n successfully_completed_at = NOW()\nWHERE token = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "f68a8917e639418a0bb952bef606c96299001e143c745ee30df99b72d1235d35"
}
+13 -4
View File
@@ -1,6 +1,15 @@
{
"files.exclude": {
"**/*~": true,
"frontend_old/**": true
}
"files.exclude": {
"**/*~": true,
".sqlx/**": true,
"_elasticsearch/**": true,
"_sql/**": true,
"frontend_old/**": true,
"includes/**": true,
"target/**": true,
"test_data/**": true,
"tmp/**": true,
"virtualenv/**": true,
"windows_dlls/**": true,
}
}
Generated
+624 -2928
View File
File diff suppressed because it is too large Load Diff
+84 -39
View File
@@ -4,12 +4,17 @@ resolver = "2"
# This block defines the workspace members.
# When we add a library or binary crate, it needs an entry here.
members = [
# Types: common strongly-typed tokens, ids, payloads, etc.
"crates/_types/composite_identifiers",
"crates/_types/enums",
"crates/_types/enums_public",
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/_types/tokens",
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
"crates/api_clients/google_sign_in",
"crates/api_clients/openai_sora_client",
"crates/api_clients/openart_client",
"crates/api_clients/runwayml_client",
"crates/api_clients/storyteller_client",
"crates/api_clients/thumbnail_generator",
# CLI tools for operations, etc.
"crates/cli/devenv/dev-database-seed",
@@ -29,31 +34,28 @@ members = [
# Shared libraries
"crates/lib/actix_cors_configs",
"crates/lib/actix_helpers",
"crates/lib/api_clients/cloud_storage",
"crates/lib/api_clients/google_drive_common",
"crates/lib/api_clients/google_sign_in",
"crates/lib/api_clients/openai_sora_client",
"crates/lib/api_clients/storyteller_client",
"crates/lib/api_clients/thumbnail_generator",
"crates/lib/bootstrap",
"crates/lib/bucket_paths",
"crates/lib/build_metadata",
"crates/lib/caching/memory_caching",
"crates/lib/caching/memory_store",
"crates/lib/caching/redis_caching",
"crates/lib/cloudflare_errors",
"crates/lib/concurrency",
"crates/lib/cookies",
"crates/lib/data/collections",
"crates/lib/data/crockford",
"crates/lib/data/datetimes",
"crates/lib/data/primitives",
"crates/lib/data/web_base64",
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
"crates/lib/dimensions",
"crates/lib/easyenv", # NB: Vendored version of https://github.com/echelon/easyenv.rs that we'll improve.
"crates/lib/elasticsearch_schema",
"crates/lib/email_sender",
"crates/lib/errors",
"crates/lib/files/archives",
"crates/lib/files/file_to_base64_url",
"crates/lib/files/filesys",
"crates/lib/files/hashing",
"crates/lib/files/images",
@@ -63,28 +65,42 @@ members = [
"crates/lib/idempotency",
"crates/lib/image_algorithms",
"crates/lib/jobs_common",
"crates/lib/jwt_light",
"crates/lib/logging",
"crates/lib/machine_learning/ml_models",
"crates/lib/machine_learning/ml_weights_registry",
"crates/lib/machine_learning/prompt_engineering",
"crates/lib/markdown",
"crates/lib/migration",
"crates/lib/mysql_queries",
"crates/lib/password",
"crates/lib/redis_common",
"crates/lib/redis_schema",
"crates/lib/server_environment",
"crates/lib/shared_service_components",
"crates/lib/sqlx_mysql_helpers",
"crates/lib/storyteller_root",
"crates/lib/subprocess_common",
"crates/lib/test_data",
"crates/lib/testing",
"crates/lib/tts_common",
"crates/lib/url_config",
"crates/lib/user_input_common",
"crates/lib/web_scrapers",
#"crates/lib/api_clients/fakeyou_client",
#"crates/lib/sqlite_queries",
#"crates/lib/machine_learning/ml_models",
# Schema (database): queries, database layouts, Redis keys, etc.
"crates/schema/database/elasticsearch_schema",
"crates/schema/database/mysql_queries",
"crates/schema/database/redis_common",
"crates/schema/database/redis_schema",
"crates/schema/database/sqlite_tasks",
#"crates/schema/database/sqlite_queries",
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
"crates/schema/public/api/artcraft_api_defs",
"crates/schema/public/composite_identifiers",
"crates/schema/public/enums",
"crates/schema/public/enums_public",
"crates/schema/public/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/schema/public/tokens",
# Services: API servers, jobs, workers, etc.
"crates/service/job/analytics_job",
@@ -105,19 +121,23 @@ members = [
"crates/service/plugins/user_traits_component",
# Vendored code
"crates/vendor/candle/candle-core",
"crates/vendor/candle/candle-datasets",
"crates/vendor/candle/candle-examples",
"crates/vendor/candle/candle-flash-attn",
"crates/vendor/candle/candle-kernels",
"crates/vendor/candle/candle-metal-kernels",
"crates/vendor/candle/candle-nn",
"crates/vendor/candle/candle-onnx",
"crates/vendor/candle/candle-pyo3",
"crates/vendor/candle/candle-transformers",
"crates/vendor/candle/tensor-tools",
"crates/vendor/fal-rs",
"crates/vendor/fal-rs/fal-derive",
"crates/vendor/fal-rs/generate",
"crates/vendor/limitation",
"crates/vendor/tauri-plugin-http",
# NB(bt,2025-05-25): Too many failures on Mac. Disable for now.
#"crates/vendor/candle/candle-core",
#"crates/vendor/candle/candle-datasets",
#"crates/vendor/candle/candle-examples",
#"crates/vendor/candle/candle-flash-attn",
#"crates/vendor/candle/candle-kernels",
#"crates/vendor/candle/candle-metal-kernels",
#"crates/vendor/candle/candle-nn",
#"crates/vendor/candle/candle-onnx",
#"crates/vendor/candle/candle-pyo3",
#"crates/vendor/candle/candle-transformers",
#"crates/vendor/candle/tensor-tools",
# NB(bt,2023-12-12): Newrelic keeps breaking with upgrades. Kill for now.
#"crates/vendor/newrelic-telemetry-sdk-rust",
@@ -157,41 +177,66 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# External libs
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
# If a crate needs to use an older/newer version, the dependency should be managed in that crate's own Cargo.toml
anyhow = "1.0.97"
actix = "0.13.5"
actix-cors = "0.6.4" # TODO(bt): Carefully upgrade this. CORS broke last time.
actix-http = "3.7.0"
actix-multipart = "0.6.2"
actix-rt = "2.10.0"
actix-service = "2.0.3"
actix-utils = "3.0.1"
actix-web = "4.7.0" # TODO(bt): We're behind and need to bump versions.
actix-web-actors = "4.3.1+deprecated"
actix-web-lab = "0.20.2"
anyhow = "1.0.98"
base64 = "0.22.1"
chrono = { version = "0.4.40", features = ["serde"] }
bytes = "1.10.1"
chrono = { version = "0.4.41", features = ["serde"] }
cookie = "0.18.1"
futures = { version = "0.3.31" }
hex-literal = "1.0.0"
indexmap = "2.9.0"
log = "0.4.27"
once_cell = "1.21.3"
rand = "0.9.0"
rand = "0.9.1"
regex = "1.11.1"
reqwest = { version = "0.12.15", features = ["json", "cookies"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.1"
serde_yaml = "0.9.34+deprecated"
sha3 = "0.10.8"
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
tempdir = "0.3.7"
tempfile = "3.20.0"
thiserror = "2.0.12"
toml = "0.8.20"
tokio = { version = "1.45.1", features = ["macros"] }
toml = "0.8.22"
url = { version = "2.5.4", features = ["serde"] }
# Internal libs
artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
crockford = { path = "crates/lib/data/crockford" }
enums = { path = "crates/_types/enums" }
enums_public = { path = "crates/_types/enums_public" }
enums = { path = "crates/schema/public/enums" }
enums_public = { path = "crates/schema/public/enums_public" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
idempotency = { path = "crates/lib/idempotency" }
images = { path = "crates/lib/files/images" }
jwt_light = { path = "crates/lib/jwt_light" }
memory_caching = { path = "crates/lib/caching/memory_caching" }
memory_store = { path = "crates/lib/caching/memory_store" }
mimetypes = { path = "crates/lib/files/mimetypes" }
openai_sora_client = { path = "crates/lib/api_clients/openai_sora_client" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
tokens = { path = "crates/_types/tokens" }
test_data = { path = "crates/lib/test_data" }
tokens = { path = "crates/schema/public/tokens" }
web_base64 = { path = "crates/lib/data/web_base64" }
+4 -3
View File
@@ -1,7 +1,7 @@
storyteller-rust
================
Our Rust monorepo, which serves both our API surface area and our downloadable client applications.
Our Artcraft monorepo, which serves our desktop application, backend infrastructure, and more.
```
."-,.__
@@ -45,5 +45,6 @@ Our Rust monorepo, which serves both our API surface area and our downloadable c
/ ,"'"\,' `/ `-.|"
```
- [documentation](./_develop/docs)
- [tools, misc](./_develop)
- [developer documentation](./_docs)
- [server API reference](https://storyteller-docs.netlify.app/)
- [tools, scripts, misc](./script)
+26
View File
@@ -0,0 +1,26 @@
Code Conventions
================
We use three languages at Artcraft: Rust, TypeScript, and Python.
## Rust style guide
We deviate from the official Rust style guide in a few places:
- We use two spaces for indentation instead of four.
- We typically keep one function or type definition per file, though this isn't a steadfast rule.
- We use a Cargo workspace to share code within our monorepo.
### Rust Preferred Libraries
We use the following Rust libraries, though we are open to migrating to alternatives:
- `actix-web` for our HTTP web server
- `reqwest` for HTTP client requests
- `sqlx` for SQL database operations
- `tokio` for asynchronous programming
## TypeScript style guide
- We use two spaces for indentation instead of four.
- We use `nx` for monorepo management
@@ -1,5 +1,5 @@
FakeYou Development Setup (bare metal)
======================================
Local Development Setup Guide (for bare metal)
==============================================
This document describes how to run the FakeYou infrastructure on your development
machine's bare metal (no containers).
@@ -0,0 +1,30 @@
-- noinspection SqlDialectInspectionForFile
-- Migration history:
-- tasks_v1.sqlite - initial version
-- tasks_v2.sqlite - added model_type (nullable)
CREATE TABLE tasks (
id TEXT NOT NULL PRIMARY KEY,
task_status TEXT NOT NULL,
task_type TEXT NOT NULL,
model_type TEXT,
provider TEXT,
provider_job_id TEXT,
frontend_subscriber_id TEXT,
frontend_subscriber_payload TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
completed_at INTEGER DEFAULT NULL
);
-- Unique Indices
CREATE UNIQUE INDEX idx_tasks_on_provider_job_id ON tasks(provider_job_id);
-- Indices
CREATE INDEX idx_tasks_on_status ON tasks(task_status);
CREATE INDEX idx_tasks_on_provider ON tasks(provider);
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party_id;
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party;
@@ -0,0 +1,9 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
ADD COLUMN maybe_external_third_party VARCHAR(16) DEFAULT NULL;
ALTER TABLE generic_inference_jobs
ADD COLUMN maybe_external_third_party_id VARCHAR(64) DEFAULT NULL;
@@ -0,0 +1,9 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP INDEX `index_maybe_external_third_party_id`
ON `generic_inference_jobs`;
DROP INDEX `index_maybe_external_third_party_and_id`
ON `generic_inference_jobs`;
@@ -0,0 +1,11 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
ADD UNIQUE INDEX index_maybe_external_third_party_and_id (maybe_external_third_party, maybe_external_third_party_id),
ALGORITHM=INPLACE, LOCK=NONE;
ALTER TABLE generic_inference_jobs
ADD INDEX index_maybe_external_third_party_id (maybe_external_third_party_id),
ALGORITHM=INPLACE, LOCK=NONE;
@@ -39,8 +39,20 @@ CREATE TABLE generic_inference_jobs (
-- Examples (may not be up to date):
-- * text_to_speech
-- * voice_conversion
-- * image_generation
-- * video_generation
-- * background_removal
inference_category VARCHAR(32) NOT NULL,
-- If the job is externally enqueued, this is the third-party service
-- responsible for the job. Typically, we won't run an inference job for
-- jobs being run on a third party.
maybe_external_third_party VARCHAR(16) DEFAULT NULL,
-- If the job is externally enqueued, this is the ID the third-party
-- service uses.
maybe_external_third_party_id VARCHAR(64) DEFAULT NULL,
-- There is an index on this column.
-- NB: See notes on inference_category. This is becoming a problematic field. We're using
-- this to load the container spin-up arguments and dependencies, but the former is being
@@ -287,11 +299,13 @@ CREATE TABLE generic_inference_jobs (
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
UNIQUE KEY (maybe_external_third_party, maybe_external_third_party_id),
KEY index_job_type (job_type),
KEY index_product_category (product_category),
KEY index_inference_category (inference_category),
KEY index_maybe_model_type_and_maybe_model_token (maybe_model_type, maybe_model_token),
KEY index_maybe_model_type (maybe_model_type),
KEY index_maybe_external_third_party_id (maybe_external_third_party_id),
KEY fk_maybe_model_token (maybe_model_token),
KEY fk_maybe_input_source_token (maybe_input_source_token),
KEY fk_maybe_input_source_token_and_type (maybe_input_source_token, maybe_input_source_token_type),
-1
View File
@@ -1 +0,0 @@
pub mod mysql_from_row;
@@ -1,14 +0,0 @@
use std::fmt::Debug;
use serde::Deserialize;
use serde::Serialize;
use crate::prefixes::TokenPrefix;
/// The primary key for user weight uploads
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
#[sqlx(transparent)]
pub struct WeightUploadToken(pub String);
impl_string_token!(WeightUploadToken);
impl_crockford_generator!(WeightUploadToken, 32usize, TokenPrefix::WeightUpload, CrockfordLower);
@@ -0,0 +1,42 @@
[package]
name = "cloud_storage"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "cloud_storage"
path = "src/lib.rs"
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
bucket_paths = { path = "../../lib/bucket_paths" }
crockford.workspace = true
easyenv = { path = "../../lib/easyenv" }
errors.workspace = true
filesys.workspace = true
hashing = { path = "../../lib/files/hashing" }
mimetypes.workspace = true
storyteller_root = { path = "../../lib/storyteller_root" }
# External
anyhow.workspace = true
log.workspace = true
env_logger = "0.11.3"
async-trait = "0.1.75"
# The "never-encode-slash" feature seems to have been removed:
# https://github.com/durch/rust-s3/pull/254
rust-s3 = { version = "0.36.0-beta.2" }
# Runtime
tokio.workspace = true
@@ -8,27 +8,33 @@ authors = [
]
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "fakeyou_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
enums = { path = "../../../_types/enums" }
errors = { path = "../../errors" }
tokens = { path = "../../../_types/tokens" }
enums.workspace = true
errors.workspace = true
tokens.workspace = true
# External
bytes = "1.3"
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
once_cell = "1.9.0"
bytes.workspace = true
chrono.workspace = true
log.workspace = true
once_cell.workspace = true
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.64"
toml = "0.5.8"
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
toml.workspace = true
# HTTP
backoff = { version = "0.4.0", features = ["futures"] } # Exponential backoff retry
+38
View File
@@ -0,0 +1,38 @@
[package]
name = "fal_client"
edition = "2021"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "fal_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
errors.workspace = true
file_to_base64_url.workspace = true
# External
anyhow.workspace = true
futures.workspace = true
log.workspace = true
reqwest.workspace = true
serde.workspace = true
tokio.workspace = true
url.workspace = true
# For now, we'll vendor in fal.
# We need to implement webhooks and the upstream doesn't support them yet.
#fal = { version = "0.3.6", features = ["endpoints"] }
fal = { path = "../../vendor/fal-rs", features = ["endpoints"] }
[dev-dependencies]
testing = { path = "../../lib/testing" }
test_data.workspace = true
@@ -0,0 +1,14 @@
#[derive(Clone)]
pub struct FalApiKey(pub String);
impl FalApiKey {
pub fn new(api_key: String) -> Self {
Self(api_key)
}
pub fn from_str(api_key: &str) -> Self {
let api_key = api_key.trim().to_string();
Self(api_key)
}
}
@@ -0,0 +1,2 @@
pub mod fal_api_key;
pub mod open_ai_api_key;
@@ -0,0 +1,15 @@
/// This is for Fal's OpenAI "Bring Your Own Key" (BYOK) endpoints.
#[derive(Clone)]
pub struct OpenAiApiKey(pub String);
impl OpenAiApiKey {
pub fn new(api_key: String) -> Self {
Self(api_key)
}
pub fn from_str(api_key: &str) -> Self {
let api_key = api_key.trim().to_string();
Self(api_key)
}
}
@@ -0,0 +1,20 @@
use crate::error::fal_error_plus::FalErrorPlus;
/// Better classify the `fal::FalError` into a more user-friendly `FalErrorPlus`.
pub fn classify_fal_error(err: fal::FalError) -> FalErrorPlus {
match err {
fal::FalError::RequestError(_) => FalErrorPlus::FalError(err),
fal::FalError::ImageError(_) => FalErrorPlus::FalError(err),
fal::FalError::SerializeError(_) => FalErrorPlus::FalError(err),
fal::FalError::StreamError(_) => FalErrorPlus::FalError(err),
fal::FalError::Other(ref inner) => {
if inner.contains("Invalid Key Authorization header format") {
FalErrorPlus::FalApiKeyError(inner.to_string())
} else if inner.contains("No user found for Key ID and Secret") {
FalErrorPlus::FalApiKeyError(inner.to_string())
} else {
FalErrorPlus::FalError(err)
}
}
}
}
@@ -0,0 +1,59 @@
use crate::error::classify_fal_error::classify_fal_error;
use std::error::Error;
use std::fmt::{Display, Formatter};
/// Additional errors that aren't included in `fal::FalError`.
#[derive(Debug)]
pub enum FalErrorPlus {
/// An error arising in the `fal` crate.
FalError(fal::FalError),
/// The fal API key is invalid.
FalApiKeyError(String),
/// Another error we didn't handle.
AnyhowError(anyhow::Error),
/// URL parse errors.
UrlParseError(url::ParseError),
/// An endpoint we don't support yet.
UnhandledEndpoint(String),
/// Error from the `reqwest` crate.
ReqwestError(reqwest::Error),
}
impl Display for FalErrorPlus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::FalError(err) => write!(f, "FalErrorPlus::FalError: {:?}", err),
Self::FalApiKeyError(reason) => write!(f, "FalErrorPlus::FalApiKeyError: {}", reason),
Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err),
Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err),
Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint),
Self::ReqwestError(err) => write!(f, "FalErrorPlus::ReqwestError: {:?}", err),
}
}
}
impl Error for FalErrorPlus {}
impl From<fal::FalError> for FalErrorPlus {
fn from(err: fal::FalError) -> Self {
classify_fal_error(err)
}
}
impl From<anyhow::Error> for FalErrorPlus {
fn from(err: anyhow::Error) -> Self {
FalErrorPlus::AnyhowError(err)
}
}
impl From<url::ParseError> for FalErrorPlus {
fn from(err: url::ParseError) -> Self {
FalErrorPlus::UrlParseError(err)
}
}
impl From<reqwest::Error> for FalErrorPlus {
fn from(err: reqwest::Error) -> Self {
FalErrorPlus::ReqwestError(err)
}
}
@@ -0,0 +1,2 @@
pub mod classify_fal_error;
pub mod fal_error_plus;
+14
View File
@@ -0,0 +1,14 @@
//! A wrapper for the "fal" crate that bakes in a few extra recipes and utilities.
extern crate fal;
// Re-export
pub mod export {
pub use fal::*;
}
pub mod creds;
pub mod error;
pub mod model;
pub mod requests;
pub mod utils;
@@ -0,0 +1,41 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::model::fal_endpoint::FalEndpoint;
use crate::model::fal_request_id::FalRequestId;
use errors::AnyhowResult;
use fal::queue::{Queue, QueueResponse};
use reqwest::Client;
use serde::de::DeserializeOwned;
#[derive(Clone, Debug)]
pub struct EnqueuedRequest {
pub fal_endpoint: FalEndpoint,
pub request_id: FalRequestId,
pub response_url: String,
pub status_url: String,
pub cancel_url: String,
}
impl EnqueuedRequest {
pub fn from_queue_response<R: DeserializeOwned>(queue: &Queue<R>) -> AnyhowResult<Self> {
let endpoint = FalEndpoint::from_queue_response(queue);
Ok(Self {
fal_endpoint: endpoint.clone(),
request_id: FalRequestId(queue.payload.request_id.clone()),
response_url: queue.payload.response_url.clone(),
status_url: queue.payload.status_url.clone(),
cancel_url: queue.payload.cancel_url.clone(),
})
}
pub fn to_queue_response<R: DeserializeOwned>(&self, api_key: &FalApiKey) -> AnyhowResult<Queue<R>> {
let client = Client::new();
let endpoint = self.fal_endpoint.url();
let payload = QueueResponse {
request_id: self.request_id.0.clone(),
response_url: self.response_url.clone(),
status_url: self.status_url.clone(),
cancel_url: self.cancel_url.clone(),
};
Ok(Queue::new(client, endpoint, api_key.0.clone(), payload))
}
}
@@ -0,0 +1,47 @@
use fal::queue::Queue;
use serde::de::DeserializeOwned;
// TODO(bt,2025-05-26): A macro to define these + tests.
const FLUX_PRO_ULTRA_TEXT_TO_IMAGE: &str = "fal-ai/flux-pro/v1.1-ultra";
const HUNYUAN_3D_2_BASE: &str = "fal-ai/hunyuan3d/v2";
const KLING16_IMAGE_TO_VIDEO: &str = "fal-ai/kling-video/v1.6/pro/image-to-video";
const MINIMAX_IMAGE_TO_VIDEO: &str = "fal-ai/minimax/image-to-video";
const RECRAFT_V3_TEXT_TO_IMAGE: &str = "fal-ai/recraft-v3";
#[derive(Clone, Debug)]
pub enum FalEndpoint {
FluxProUltraTextToImage,
Hunyuan3d2Base,
Kling16ImageToVideo,
Minimax01ImageToVideo,
RecraftV3TextToImage,
Other(String),
}
impl FalEndpoint {
pub fn from_queue_response<R: DeserializeOwned>(queue: &Queue<R>) -> Self {
match queue.endpoint.as_str() {
FLUX_PRO_ULTRA_TEXT_TO_IMAGE => FalEndpoint::FluxProUltraTextToImage,
HUNYUAN_3D_2_BASE => FalEndpoint::Hunyuan3d2Base,
KLING16_IMAGE_TO_VIDEO => FalEndpoint::Kling16ImageToVideo,
MINIMAX_IMAGE_TO_VIDEO => FalEndpoint::Minimax01ImageToVideo,
RECRAFT_V3_TEXT_TO_IMAGE => FalEndpoint::RecraftV3TextToImage,
_ => FalEndpoint::Other(queue.endpoint.clone()),
}
}
pub fn url(&self) -> &str {
match self {
Self::FluxProUltraTextToImage => FLUX_PRO_ULTRA_TEXT_TO_IMAGE,
Self::Hunyuan3d2Base => HUNYUAN_3D_2_BASE,
Self::Kling16ImageToVideo => KLING16_IMAGE_TO_VIDEO,
Self::Minimax01ImageToVideo => MINIMAX_IMAGE_TO_VIDEO,
Self::RecraftV3TextToImage => RECRAFT_V3_TEXT_TO_IMAGE,
Self::Other(url) => url,
}
}
}
@@ -0,0 +1,13 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::model::fal_endpoint::FalEndpoint;
/// A strongly typed request ID for the Fal API.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
pub struct FalRequestId(pub String);
impl Display for FalRequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
@@ -0,0 +1,3 @@
pub mod enqueued_request;
pub mod fal_endpoint;
pub mod fal_request_id;
@@ -0,0 +1,2 @@
pub mod queue;
pub mod webhook;
@@ -0,0 +1,69 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::hunyuan3d::v2::v2;
use fal::endpoints::fal_ai::hunyuan3d::v2::Hunyuan3DInput;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Hunyuan2Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub api_key: &'a FalApiKey,
}
pub async fn enqueue_hunyuan2_image_to_3d<P: AsRef<Path>>(args: Hunyuan2Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let request = Hunyuan3DInput {
input_image_url: image_url,
textured_mesh: Some(true),
guidance_scale: None,
num_inference_steps: None,
octree_resolution: None,
seed: None,
};
let result = v2(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Hunyuan2Args {
image_path: image,
api_key: &api_key,
};
let result = enqueue_hunyuan2_image_to_3d(args).await?;
Ok(())
}
}
@@ -0,0 +1,69 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::v1_1_ultra;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::FluxProUltraTextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct FluxPro11UltraTextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_flux_pro_11_ultra_text_to_image(args: FluxPro11UltraTextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = FluxProUltraTextToImageInput {
prompt: args.prompt.to_string(),
// Maybe expose
aspect_ratio: None, // Default is "16:9"
num_images: Some(1), // Default is 1
seed: None,
raw: Some(true), // Generate less processed, more natural-looking images. Default is false.
// Maybe abstract
enable_safety_checker: Some(false),
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
// Constants
output_format: Some("png".to_string()),
sync_mode: None, // Synchronous / slow
};
let result = v1_1_ultra(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_pro() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = FluxPro11UltraTextToImageArgs {
prompt: "a corgi smiles at the camera, inside retro video game store, lots of sci-fi props from Cowboy Bebop, photorealistic",
api_key: &api_key,
};
let result = enqueue_flux_pro_11_ultra_text_to_image(args).await?;
Ok(())
}
}
@@ -0,0 +1,63 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::recraft_v3::recraft_v3;
use fal::endpoints::fal_ai::recraft_v3::ImageSizeProperty;
use fal::endpoints::fal_ai::recraft_v3::TextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct Recraft3TextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_recraft3_text_to_image(args: Recraft3TextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = TextToImageInput {
image_size: Some(ImageSizeProperty::SquareHd),
prompt: args.prompt.to_string(),
style: None, // eg. "realistic_image"
style_id: None, // LoRA.
colors: None, // Preferable colors. We can offer this later.
};
let result = recraft_v3(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_recraft3() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Recraft3TextToImageArgs {
prompt: "a corgi looks out over the water",
api_key: &api_key,
};
let result = enqueue_recraft3_text_to_image(args).await?;
Ok(())
}
}
@@ -0,0 +1,2 @@
pub mod enqueue_flux_pro_11_ultra_text_to_image;
pub mod enqueue_recraft3_text_to_image;
@@ -0,0 +1,4 @@
pub mod enqueue_hunyuan2_image_to_3d;
pub mod image_gen;
pub mod remove_background_rembg;
pub mod video_gen;
@@ -0,0 +1,75 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::RemoveBackgroundInput;
use fal::prelude::fal_ai::imageutils::rembg::rembg;
use fal::prelude::*;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::path::Path;
use url::Url;
pub struct RemBgResponse {
pub image_url: Url,
}
/// remove background using `fal_ai/imageutils/rembg`
pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &FalApiKey) -> Result<RemBgResponse, FalErrorPlus> {
let image_url = file_to_base64_url(image_path)?;
let request = RemoveBackgroundInput {
image_url,
crop_to_bbox: None,
sync_mode: None
};
let result = rembg(request)
.with_api_key(&api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
let mut stream = result.stream(true).await?;
while let Some(status) = stream.next().await {
let status = status?;
if status.status == Status::Completed {
break;
}
}
let output = result.response().await?;
let url = Url::parse(&output.image.url)?;
Ok(RemBgResponse {
image_url: url,
})
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::remove_background_rembg::remove_background_rembg;
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_remove_background_rembg() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let result = remove_background_rembg(image, &api_key).await?;
Ok(())
}
}
@@ -0,0 +1,102 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Kling16ProArgs<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling16ProDuration,
pub aspect_ratio: Kling16ProAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16ProDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16ProAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_16_pro_image_to_video<P: AsRef<Path>>(args: Kling16ProArgs<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let duration = match args.duration{
Kling16ProDuration::Default => None,
Kling16ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling16ProDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling16ProAspectRatio::Square => Some("1:1".to_string()),
Kling16ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling16ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let request = ProImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
aspect_ratio,
duration,
// Maybe expose these later
cfg_scale: None,
negative_prompt: None,
tail_image_url: None,
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_kling16_video() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Kling16ProArgs {
image_path: image,
prompt: "a corgi looks out over the water",
api_key: &api_key,
duration: Kling16ProDuration::Default,
aspect_ratio: Kling16ProAspectRatio::Square,
};
let result = enqueue_kling_16_pro_image_to_video(args).await?;
Ok(())
}
}
@@ -0,0 +1,70 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::minimax::image_to_video::image_to_video;
use fal::endpoints::fal_ai::minimax::image_to_video::ImageToVideoRequest;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Minimax01Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
}
pub async fn enqueue_minimax_01_image_to_video<P: AsRef<Path>>(args: Minimax01Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let request = ImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
prompt_optimizer: None,
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(classify_fal_error(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::video_gen::enqueue_minimax_01_image_to_video::{enqueue_minimax_01_image_to_video, Minimax01Args};
use errors::AnyhowResult;
use std::fs::read_to_string;
use testing::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_minimax_video() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Minimax01Args {
image_path: image,
prompt: "a dog jumps into the water",
api_key: &api_key,
};
let result = enqueue_minimax_01_image_to_video(args).await?;
println!("Request ID: {:?}", result.request_id);
Ok(())
}
}
@@ -0,0 +1,2 @@
pub mod enqueue_kling_16_pro_image_to_video;
pub mod enqueue_minimax_01_image_to_video;
@@ -0,0 +1,106 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::flux::dev::dev;
use fal::endpoints::fal_ai::flux::dev::DevTextToImageInput;
use fal::prelude::fal_ai::flux::dev::ImageSizeProperty;
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1DevArgs<'a, U: IntoUrl> {
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
pub aspect_ratio: Flux1DevAspectRatio,
pub num_images: Flux1DevNumImages,
}
#[derive(Copy, Clone, Debug)]
pub enum Flux1DevAspectRatio {
Square, // 1:1
SquareHd, // 1:1
LandscapeFourByThree, // 4:3
LandscapeSixteenByNine, // 16:9
PortraitThreeByFour, // 3:4
PortraitNineBySixteen, // 9:16
//Custom { width: u32, height: u32 }, // TODO
}
#[derive(Copy, Clone, Debug)]
pub enum Flux1DevNumImages {
One, // Default
Two,
Three,
Four,
}
pub async fn enqueue_flux_1_dev_text_to_image_webhook<U: IntoUrl>(
args: Flux1DevArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
Flux1DevNumImages::One => 1,
Flux1DevNumImages::Two => 2,
Flux1DevNumImages::Three => 3,
Flux1DevNumImages::Four => 4,
};
let image_size = match args.aspect_ratio {
Flux1DevAspectRatio::Square => ImageSizeProperty::Square,
Flux1DevAspectRatio::SquareHd => ImageSizeProperty::SquareHd,
Flux1DevAspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
Flux1DevAspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
Flux1DevAspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
Flux1DevAspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
};
let request = DevTextToImageInput {
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size),
// Maybe abstract
enable_safety_checker: Some(false),
// Maybe expose
seed: None,
guidance_scale: None,
num_inference_steps: None,
// Constants
sync_mode: None, // Synchronous / slow
};
let result = dev(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::enqueue_flux_1_dev_text_to_image_webhook::{enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs, Flux1DevAspectRatio, Flux1DevNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_1_dev() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Flux1DevArgs {
prompt: "a giant robot fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: Flux1DevNumImages::One,
aspect_ratio: Flux1DevAspectRatio::LandscapeFourByThree
};
let result = enqueue_flux_1_dev_text_to_image_webhook(args).await?;
Ok(())
}
}
@@ -0,0 +1,105 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::flux::schnell::schnell;
use fal::endpoints::fal_ai::flux::schnell::SchnellTextToImageInput;
use fal::prelude::fal_ai::flux::schnell::ImageSizeProperty;
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Flux1SchnellArgs<'a, U: IntoUrl> {
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
pub aspect_ratio: Flux1SchnellAspectRatio,
pub num_images: Flux1SchnellNumImages,
}
#[derive(Copy, Clone, Debug)]
pub enum Flux1SchnellAspectRatio {
Square, // 1:1
SquareHd, // 1:1
LandscapeFourByThree, // 4:3
LandscapeSixteenByNine, // 16:9
PortraitThreeByFour, // 3:4
PortraitNineBySixteen, // 9:16
//Custom { width: u32, height: u32 }, // TODO
}
#[derive(Copy, Clone, Debug)]
pub enum Flux1SchnellNumImages {
One, // Default
Two,
Three,
Four,
}
pub async fn enqueue_flux_1_schnell_text_to_image_webhook<U: IntoUrl>(
args: Flux1SchnellArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
Flux1SchnellNumImages::One => 1,
Flux1SchnellNumImages::Two => 2,
Flux1SchnellNumImages::Three => 3,
Flux1SchnellNumImages::Four => 4,
};
let image_size = match args.aspect_ratio {
Flux1SchnellAspectRatio::Square => ImageSizeProperty::Square,
Flux1SchnellAspectRatio::SquareHd => ImageSizeProperty::SquareHd,
Flux1SchnellAspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
Flux1SchnellAspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
Flux1SchnellAspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
Flux1SchnellAspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
};
let request = SchnellTextToImageInput {
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size),
// Maybe abstract
enable_safety_checker: Some(false),
// Maybe expose
seed: None,
num_inference_steps: None,
// Constants
sync_mode: None, // Synchronous / slow
};
let result = schnell(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::enqueue_flux_1_schnell_text_to_image_webhook::{enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs, Flux1SchnellAspectRatio, Flux1SchnellNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_1_schnell() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Flux1SchnellArgs {
prompt: "a giant robot fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: Flux1SchnellNumImages::One,
aspect_ratio: Flux1SchnellAspectRatio::LandscapeSixteenByNine,
};
let result = enqueue_flux_1_schnell_text_to_image_webhook(args).await?;
Ok(())
}
}
@@ -0,0 +1,104 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::flux_pro::v1_1::{v1_1, FluxProPlusTextToImageInput, ImageSizeProperty};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxPro11Args<'a, U: IntoUrl> {
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
pub aspect_ratio: FluxPro11AspectRatio,
pub num_images: FluxPro11NumImages,
}
#[derive(Copy, Clone, Debug)]
pub enum FluxPro11AspectRatio {
Square, // 1:1
SquareHd, // 1:1
LandscapeFourByThree, // 4:3
LandscapeSixteenByNine, // 16:9
PortraitThreeByFour, // 3:4
PortraitNineBySixteen, // 9:16
//Custom { width: u32, height: u32 }, // TODO
}
#[derive(Copy, Clone, Debug)]
pub enum FluxPro11NumImages {
One, // Default
Two,
Three,
Four,
}
pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
args: FluxPro11Args<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
FluxPro11NumImages::One => 1,
FluxPro11NumImages::Two => 2,
FluxPro11NumImages::Three => 3,
FluxPro11NumImages::Four => 4,
};
let image_size = match args.aspect_ratio {
FluxPro11AspectRatio::Square => ImageSizeProperty::Square,
FluxPro11AspectRatio::SquareHd => ImageSizeProperty::SquareHd,
FluxPro11AspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
FluxPro11AspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
FluxPro11AspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
FluxPro11AspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
};
let request = FluxProPlusTextToImageInput {
prompt: args.prompt.to_string(),
num_images: Some(num_images),
image_size: Some(image_size),
// Maybe expose
seed: None,
// Maybe abstract
enable_safety_checker: Some(false),
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
// Constants
output_format: Some("png".to_string()),
sync_mode: None, // Synchronous / slow
};
let result = v1_1(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::enqueue_flux_pro_11_text_to_image_webhook::{enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, FluxPro11AspectRatio, FluxPro11NumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_11_pro() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = FluxPro11Args {
prompt: "a giant red panda fighting a dragon in a futuristic city",
api_key: &api_key,
webhook_url: "https://example.com/webhook",
num_images: FluxPro11NumImages::One,
aspect_ratio: FluxPro11AspectRatio::LandscapeSixteenByNine,
};
let result = enqueue_flux_pro_11_text_to_image_webhook(args).await?;
Ok(())
}
}
@@ -0,0 +1,74 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::{v1_1_ultra, AspectRatioProperty, FluxProUltraTextToImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct FluxPro11UltraArgs<'a, U: IntoUrl> {
pub prompt: &'a str,
pub webhook_url: U,
pub api_key: &'a FalApiKey,
pub aspect_ratio: FluxPro11UltraAspectRatio,
pub num_images: FluxPro11UltraNumImages,
}
#[derive(Copy, Clone, Debug)]
pub enum FluxPro11UltraAspectRatio {
Square, // 1:1
LandscapeFourByThree, // 4:3
LandscapeSixteenByNine, // 16:9
PortraitThreeByFour, // 3:4
PortraitNineBySixteen, // 9:16
//Custom { width: u32, height: u32 }, // TODO
}
#[derive(Copy, Clone, Debug)]
pub enum FluxPro11UltraNumImages {
One, // Default
Two,
Three,
Four,
}
pub async fn enqueue_flux_pro_11_ultra_text_to_image_webhook<U: IntoUrl>(
args: FluxPro11UltraArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
FluxPro11UltraNumImages::One => 1,
FluxPro11UltraNumImages::Two => 2,
FluxPro11UltraNumImages::Three => 3,
FluxPro11UltraNumImages::Four => 4,
};
let aspect_ratio = match args.aspect_ratio {
FluxPro11UltraAspectRatio::Square => AspectRatioProperty::Property_1_1,
FluxPro11UltraAspectRatio::LandscapeFourByThree => AspectRatioProperty::Property_4_3,
FluxPro11UltraAspectRatio::LandscapeSixteenByNine => AspectRatioProperty::Property_16_9,
FluxPro11UltraAspectRatio::PortraitThreeByFour => AspectRatioProperty::Property_3_4,
FluxPro11UltraAspectRatio::PortraitNineBySixteen => AspectRatioProperty::Property_9_16,
};
let request = FluxProUltraTextToImageInput {
prompt: args.prompt.to_string(),
num_images: Some(num_images),
aspect_ratio: Some(aspect_ratio),
// Maybe expose
seed: None,
raw: Some(true), // Generate less processed, more natural-looking images. Default is false.
// Maybe abstract
enable_safety_checker: Some(false),
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
// Constants
output_format: Some("png".to_string()),
sync_mode: None, // Synchronous / slow
};
let result = v1_1_ultra(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
@@ -0,0 +1,133 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::gpt_image_1::edit_image::byok::{gpt_edit_image, GptEditImageRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
// Request
pub image_urls: Vec<String>,
pub prompt: &'a str,
pub image_size: GptEditImageSize,
pub num_images: GptEditImageNumImages,
pub quality: GptEditImageQuality,
// Fulfillment
pub api_key: &'a FalApiKey,
pub openai_api_key: &'a OpenAiApiKey,
pub webhook_url: V,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageSize{
Auto,
Square,
Horizontal,
Vertical,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageQuality {
Auto,
Low,
Medium,
High,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageNumImages{
One,
Two,
Three,
Four,
}
pub async fn enqueue_gpt_image_1_edit_image_webhook<V: IntoUrl>(
args: GptEditImageByokArgs<'_, V>
) -> Result<WebhookResponse, FalErrorPlus> {
// auto, 1024x1024, 1536x1024, 1024x1536
let image_size = match args.image_size {
GptEditImageSize::Auto => "auto",
GptEditImageSize::Square => "1024x1024",
GptEditImageSize::Horizontal => "1536x1024",
GptEditImageSize::Vertical => "1024x1536",
};
let quality = match args.quality {
GptEditImageQuality::Auto => "auto",
GptEditImageQuality::Low => "low",
GptEditImageQuality::Medium => "medium",
GptEditImageQuality::High => "high",
};
let num_images = match args.num_images {
GptEditImageNumImages::One => 1,
GptEditImageNumImages::Two => 2,
GptEditImageNumImages::Three => 3,
GptEditImageNumImages::Four => 4,
};
let request = GptEditImageRequest {
image_urls: args.image_urls,
prompt: args.prompt.to_string(),
image_size: image_size.to_string(),
num_images,
quality: quality.to_string(),
openai_api_key: args.openai_api_key.0.to_string(),
};
let result = gpt_edit_image(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::{enqueue_gpt_image_1_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages, GptEditImageQuality, GptEditImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, GRASSY_HILL_TRANSPARENT_IMAGE_URL};
#[tokio::test]
#[ignore]
async fn test_gpt_image_1_edit_image() -> AnyhowResult<()> {
let image_url = "https://cdn-2.fakeyou.com/media/3/4/h/f/s/34hfsmt8e38rvne6mwa4pwbxr6292sgy/image_34hfsmt8e38rvne6mwa4pwbxr6292sgy.png";
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let fal_api_key = FalApiKey::from_str(&secret);
let secret = read_to_string("/Users/bt/Artcraft/credentials/openai_api_key.txt")?;
let open_ai_api_key = OpenAiApiKey::from_str(&secret);
let args = GptEditImageByokArgs {
image_urls: vec![
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
GHOST_IMAGE_URL.to_string(),
GRASSY_HILL_TRANSPARENT_IMAGE_URL.to_string(),
],
prompt: "put the man and the ghost on the grassy hill. the man is scared of the friendly ghost.",
api_key: &fal_api_key,
openai_api_key: &open_ai_api_key,
webhook_url: "https://example.com/webhook",
image_size: GptEditImageSize::Horizontal,
num_images: GptEditImageNumImages::One,
quality: GptEditImageQuality::High,
};
let result = enqueue_gpt_image_1_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -0,0 +1,6 @@
pub mod enqueue_flux_1_dev_text_to_image_webhook;
pub mod enqueue_flux_1_schnell_text_to_image_webhook;
pub mod enqueue_flux_pro_11_text_to_image_webhook;
pub mod enqueue_flux_pro_11_ultra_text_to_image_webhook;
pub mod enqueue_gpt_image_1_edit_image_webhook;
pub mod remove_background_rembg_webhook;
@@ -0,0 +1,61 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct RemoveBackgroundRembgWebhookArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub api_key: &'a FalApiKey
}
pub async fn remove_background_rembg_webhook<U: IntoUrl, V: IntoUrl>(
args: RemoveBackgroundRembgWebhookArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let image_url = args.image_url.into_url()?;
let request = RemoveBackgroundInput {
image_url: image_url.to_string(),
crop_to_bbox: None,
sync_mode: None
};
let result = rembg(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::remove_background_rembg_webhook::remove_background_rembg_webhook;
use crate::requests::webhook::image::remove_background_rembg_webhook::RemoveBackgroundRembgWebhookArgs;
use std::fs::read_to_string;
use test_data::web::image_urls::ERNEST_SCARED_STUPID_IMAGE_URL;
#[tokio::test]
#[ignore] // NB: Manually test, don't run in CI!
async fn test_rembg_request() -> anyhow::Result<()> {
// XXX: Don't commit secrets!
let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&api_key);
let args = RemoveBackgroundRembgWebhookArgs {
image_url: ERNEST_SCARED_STUPID_IMAGE_URL,
webhook_url: "https://api.storyteller.ai/webhook",
api_key: &api_key,
};
let response = remove_background_rembg_webhook(args).await?;
println!("{:?}", response);
Ok(())
}
}

Some files were not shown because too many files have changed in this diff Show More