Compare commits

..
Author SHA1 Message Date
Hanashi 2cfcb6131d default launch scripts to dev server 2026-08-08 19:16:32 -04:00
Hanashi 480c073c54 devex: easier to target backend in dev 2026-08-08 16:40:02 -04:00
hanashi 9646a791aa minimax feature (#1865) 2026-08-08 16:34:49 -04:00
BFlat 4d839d3b8b Library/video promptbox: bulk "Add tags" action + default video mode to Seedance 2.5 (#1864)
- Select multiple items in the library and add tags to all of them at
once
- Tag suggestions pop up as you type, based on your existing tags
- Fixed the selection bar looking squished (buttons no longer wrap onto
two lines)
- Video generation now defaults to Seedance 2.5 (was 2.0)
- Omni Reference is the default video input mode for everyone
2026-08-08 15:41:13 +07:00
hanashi b7f354face fix seedance 2.5 estimates (#1863) 2026-08-08 00:46:06 -04:00
hanashi c888a494b9 seedance 2.5 bindings (#1862) 2026-08-07 23:41:44 -04:00
Tanish Manku a048458956 3D: Animation clip blending (#1861) 2026-08-07 18:48:54 -07:00
BFlat d598896e40 Model picker: add scroll fade + chevron indicators to submenu flyout (#1860)
- Bring the main panel's top/bottom gradient fades and bouncing scroll
hint arrows over to the family flyout list.
2026-08-07 23:48:20 +07:00
BFlat af9dfa556f Model picker: grouped submenus, color icons, capability tags (#1859)
- Model pickers on the create pages now group models by family
(Seedance, Veo, Kling, etc.) with a flyout submenu per family, so the
list is no longer one long scroll
- The flyout opens on hover or click, and automatically scrolls the
currently selected model into view
- New setting in **Settings → General** ("Group models by family") to
switch back to the classic flat list. Grouped is the default and works
in both the webapp and the desktop app
- Model rows now use full-color brand icons, while the picker button
keeps its existing look
- Video models show capability tags: **Audio Support**, **Start/End**,
and **Reference**
- Short model lists (under 8 models) stay flat, and mobile keeps the
existing bottom-sheet list
- Fix: videos in the media viewer can now go fullscreen (the expand
button was disabled)
2026-08-07 23:36:59 +07:00
Tanish Manku ea0de174c9 3D: Fix CI/CD deploy (worker vite build issue) (#1858) 2026-08-07 08:47:10 -07:00
Tanish MankuandBFlat a1435df07d 3D: Animations support + UI (#1856)
Co-authored-by: BFlat <bombayapirat@gmail.com>
2026-08-06 22:48:35 -07:00
hanashi a7a32a3862 minimax worker endpoints (#1855) 2026-08-07 01:24:20 -04:00
BFlat bb10c993a4 Hotfix: Minimax landing scroll issue 2026-08-07 10:48:53 +07:00
BFlat c6474edd5c Website: MiniMax H3 marketing page (#1854)
## Summary

New marketing page at /minimax-h3 where visitors can try MiniMax H3 for
free,
right on the page.

- Hero is a working promptbox, locked to MiniMax H3 with a "Free" badge
- Full settings available: aspect ratio, resolution, duration, sound,
start/end frames
- New users sign up in a modal when they hit Generate (Google or email),
then their prompt runs immediately without leaving the page
- Generated videos show up right below the promptbox, with a queue
notice and a CTA to keep creating in the app while they wait
- Example videos from MiniMax's announcement, self-hosted with
thumbnails, only load when played, credited to MiniMax
- Overview, highlights, prompt tips, and FAQ copy making the free offer
clear Website promptbox updated to match the current webapp promptbox
(reference deck, drag & drop, focus mode, same buttons)
- SEO, redirects, and URL aliases (/minimax, /hailuo) wired up
2026-08-07 10:41:07 +07:00
hanashi 9020f0aa5b minimax turbo (#1852) 2026-08-06 18:21:28 -04:00
BFlat c2e3c27a72 Model list: names, icons, descriptions for new omni gen models (MiniM… (#1851)
…ax H3, Flux 3, Veo 3.1 Lite, Vidu Q3, 3D/world)
2026-08-06 23:32:37 +07:00
BFlat 22847961e1 Admin dashboard: bulk ban (#1850) 2026-08-06 22:58:37 +07:00
BFlat f72981875c Website: beta sign up remove scripts (#1849) 2026-08-06 13:07:13 +07:00
BFlat 39e89c76e8 Website: beta sign up page (#1848) 2026-08-06 12:43:47 +07:00
hanashi a473be78bd new endpoint for media files by job token (#1846) 2026-08-04 23:13:30 -04:00
hanashi 36cfba0000 media files - record job token (#1845) 2026-08-04 22:09:08 -04:00
hanashi a5f59d7995 Flux3 (#1844) 2026-08-04 15:12:32 -04:00
bflatastic 6329045350 Hotfix: mention autocomplete popover colors + darker chips on mobile 2026-08-04 23:19:49 +07:00
bflatastic cbfed0017b Hotfix: mobile mention popover positioning, chip replace closing early & duplicating text 2026-08-04 23:08:46 +07:00
bflatastic 099e3cb27b Hotfix: mobile mention autocomplete taps not selecting 2026-08-04 22:51:47 +07:00
BFlat 9c18f746d4 Character preview & mobile drawer improvements (#1843)
- Characters modal: new eye button on character cards to view the image
full size
- Mention chip menu: tapping the avatar thumbnail also opens the
full-size view
- Card buttons (view/edit/delete) are always visible on touch devices
instead of hover-only
- Mobile: Characters open in a bottom drawer with a 2-column grid,
matching the other settings drawers
- All mobile drawers now have a close (X) button
- All mobile drawers can be pulled down to dismiss - the sheet follows
your thumb and closes from where you let go
2026-08-04 22:38:32 +07:00
BFlat e4fc9446d8 Mention fixes (promptbox) (#1842)
- Mobile: mention autocomplete list can now be scrolled - dragging no
longer selects an item, only a tap does
- Fixed taps on autocomplete items sometimes not registering on
Android/iOS
- Deleting the space after a mention now reopens the autocomplete next
to the mention instead of at the top of the screen
- Clicking a mention chip while its menu is open now closes the menu
instead of closing and reopening it (desktop + mobile)
2026-08-04 21:08:21 +07:00
hanashi 37b7f3e88f Minimax (#1840) 2026-08-04 05:31:17 -04:00
hanashi 60b4148179 web + API for omni gen endpoints (#1836) 2026-08-03 16:22:16 -04:00
hanashi a4d9a4d199 this fixes another set of negative margins (#1834) 2026-08-02 18:22:04 -04:00
hanashi 2b8cc5ef8d margins for video references were costing us money, fixing (#1833) 2026-08-02 17:29:06 -04:00
hanashi 9122be6752 we're losing money on seedance in this configuration (need to fix) (#1832) 2026-08-02 17:02:49 -04:00
hanashi 31054141cc costs for seedance 2.5 are costing us money (#1831)
we're losing money on this model, gotta plug it (there are other holes
to fix too)
2026-08-02 15:58:08 -04:00
BFlat 6b1b4c7b0d temporarily hide downloads (#1830) 2026-08-02 10:57:54 +07:00
BFlat 5f46ee0e7e Added missing model names & icons mappings (#1829)
- Added missing display names for Seedance 2.5 Preview and ~25 other
models (Vidu Q3, Veo 3.1 Lite, Seedream 5.0 Pro, new Hunyuan 3D 3.1
variants, Marble 1.x, and more) so the task queue and history show
proper names instead of raw IDs.
- Vidu, Recraft, and SeedEdit models now show their brand icons instead
of the generic one.
2026-08-02 07:47:43 +07:00
BFlat 9cdad367c0 model selector 2.5 coming soon removal and reference deplicated tokens bug fix (#1828)
## Fixes & improvements

### Model picker
- Removed the "Seedance 2.5 - SOON" placeholder from the video model
picker; it was confusing now that Seedance 2.5 Preview is live.

### "Not all media files could be found" error
- Fixed generations failing when the same media file ended up twice in
one reference list (e.g. added as two references, or the same file
uploaded twice).
- Requests now automatically drop duplicate media references before
sending - applies to image, video, audio, mesh, and splat generation on
both the web app and the desktop app.

### Gallery picker UX
- Media already added to a slot is now greyed out with an "Added" badge
in the library picker, so the same file can't be added twice to the same
field.
- Applies per slot across the create pages (video, image, audio, 3D
object, world) on web and desktop - references, start frame, end frame,
video refs, audio refs, and multi-view slots.
- Reusing the same image across different slots (e.g. start *and* end
frame, or two 3D views) is still allowed.
2026-08-02 07:32:41 +07:00
hanashi c026a7c572 seedance 2.5 preview in Omni API (#1827) 2026-08-01 16:07:05 -04:00
hanashi 62051aa49f create material (#1826) 2026-08-01 10:48:19 -04:00
hanashi a71a4b9613 seedance2p5 (#1825) 2026-07-31 18:12:27 -04:00
hanashi 9737f0ef88 rename client (#1824) 2026-07-31 15:10:30 -04:00
hanashi 0eddb659fa fixup 3d on admin dash, add databox (#1823) 2026-07-30 20:20:44 -04:00
hanashi 17c12836ad seedream 5 plus (#1822) 2026-07-30 18:25:19 -04:00
BFlat 7654e95636 Desktop: drag & drop files onto the promptbox on create pages (#1821)
### What
- Drag files onto the promptbox on the Image, Video, and Audio create
pages to add them as references - same look and behavior as the webapp
- On those pages the promptbox is now the only drop target; dropping
elsewhere does nothing
- Every other page keeps the existing upload-modal behavior

### Notes
- The webapp's drop code doesn't work as-is on desktop - Tauri
intercepts file drags before the page sees them, so desktop needed its
own path
- Both now share one component, so the two apps stay in sync going
forward
- Model limits still apply: each box only accepts the reference types
its model supports, and existing slot/duration caps are unchanged
2026-07-30 22:37:34 +07:00
BFlat 1bb31f42d7 Bug fix: hand-typed mentions (#1819)
- **Hand-typed mentions always take the character's exact name** -
typing `@person` for a character named `Person` now converts to
`@Person` as you type, so the tag binds correctly instead of silently
failing at generation time.
2026-07-30 08:05:44 +07:00
BFlat bc4b3a6c1f Bug fix: mention textarea & reference deck (#1818)
- **Prompt input no longer freezes at a character tag** - the caret
could get stuck inside a chip (e.g. after deleting the space behind it),
blocking all typing.
- **Reference add-menu no longer hides behind the sidebar** - it now
renders above everything instead of getting clipped by the promptbox
layers.
- **Character autocomplete is scrollable in focus mode** - the
fullscreen prompt's scroll lock was blocking it; same fix for the chip
menu's Replace list.
2026-07-30 07:43:40 +07:00
BFlat 13b5e7b5c2 Mention textarea focus & dropdown fixes (#1817)
- Fix popovers (model selector etc.) closing instantly when the mention
textarea was focused
- Fix mention autocomplete not clickable in prompt focus mode
- Fix chip "Replace" reopening the autocomplete - now confirms
immediately
- Fix autocomplete appearing at top-left of screen when caret is next to
a leading chip
- Don't open the autocomplete when clicking the caret onto an
already-confirmed chip
2026-07-29 23:09:26 +07:00
BFlat 4ff5bdf475 Webapp: mobile audio ref fixes (#1816)
- **Fixed: picking an audio reference on mobile opened the photo/video
picker.** iPhones don't have an audio library, so the browser fell back
to Photos - the only thing you could "attach as audio" was a video, and
it silently uploaded as an audio ref. Audio pickers now open the Files
app filtered to real audio files (MP3, WAV, M4A, FLAC…).
- **Non-audio files are now rejected with a clear error toast** instead
of being silently attached (also covers drag & drop and Android's "any
file" picker).
- **Fixed: limit warnings never appeared in the web app.** Errors like
"Total audio duration cannot exceed 15s" from shared components were
firing into a toast system the web app doesn't render. They're now
routed to the app's own toasts, so length/limit errors actually show up.
- Applies to the create video, create audio, and desktop reference deck
flows
2026-07-29 22:17:04 +07:00
BFlat e1b16cf2de Character mention chips + mention fixes (#1815)
### New stuff
- Character `@mentions` in the video promptbox now show as **chips with
a thumbnail**, so you can see exactly which character is attached to the
prompt
- Clicking a chip opens a menu to **Replace** (pick another character),
**Preview** (view their image), or **Remove** the mention
- Chips behave like a single unit - one backspace deletes the whole
mention, and removing/replacing can be undone with Ctrl+Z
- Copying prompt text still gives clean plain text (`@name ...`), and
pasting it back restores the chips
- When two characters share a name, the one you actually picked is now
the one that gets sent

### Fixes
- Mobile: the `@` autocomplete no longer goes off-screen - it stays
inside the viewport, flips below the caret when needed, and taps select
reliably
- Focus mode: the autocomplete no longer pushes the prompt sideways /
causes overflow
- Enter-to-generate now works while the mention editor is active on the
webapp
2026-07-29 21:43:02 +07:00
BFlat 2d7bf65a23 Bug fix: created characters token (#1813)
- Create flow stored `inference_job_token` as the `character_token`, so
mentioning
a new character sent a bogus token - backend drops it and name-matches
the
  raw `@name` against stale characters.
- Don't add pending characters to the mention store; they aren't
generatable yet.
- Keep the pending poll running after the modal closes and sync real
tokens into
  the store once the character is active.
- Remove unused `addCharacter` store action.
- Same fix in all three copies: webapp, website, shared `promptbox` lib.
2026-07-28 22:03:30 +07:00
Brandon Thomas 6ee5518c55 api keys for everyone (#1811) 2026-07-27 14:55:54 -04:00
Brandon Thomas f708402e8a grab billing data for reports (#1810) 2026-07-27 10:03:13 -04:00
BFlat ffa86f0ba8 Webapp: Video Frame Extractor (#1809)
Ports the Video Frame Extractor from the desktop app to the webapp at
`/frame-extractor`, with a reworked UI.

### What's new
- New Frame Extractor page: upload a video, pick one from your library,
or drag and drop
- Scrub to any moment and capture the exact frame at the playhead with
one click
- Frame stepping buttons and keyboard shortcuts (arrows to step, Space
to play/pause, C or Enter to capture)
- Burst capture: grab a sequence of frames to find the sharpest one,
with progress and cancel
- Each captured frame can be used as an image reference, animated into a
video, saved to the library, or downloaded
- Save all and clear actions for captured frames

### Integrations
- "Extract frames" action on library videos (selection bar and lightbox)
opens the extractor with that video preloaded
- Sends frames straight into the create-image / create-video reference
deck, same flow as "Send to prompt"
- New sidebar entry (purple NEW badge) and home page card
- Page is public; saving and references prompt sign-up when logged out
2026-07-27 18:52:47 +07:00
BFlat b741cdd76c Webapp: send library media to the create-image / create-video promptbox (#1807)
- Adds a **Send to prompt** action to the library's multi-select bar
with two
  targets: **Create image** and **Create video**
- Selected images are attached as reference images on the target page
without
touching the user's typed prompt or settings; selection is cleared and
the
  app navigates to the create page
- Selected videos can be sent to the video prompt as reference videos;
models
without video-reference support (only Seedance has it) get an
explanatory
  toast instead of a silent drop
- Media travels via `pendingRefImages` / `pendingRefVideos` store slots
(mirroring the existing `pendingRecreate` pattern) and is merged on the
  receiving page once its model list is loaded, so the **real per-model
reference caps** apply - fixes silent no-ops when refs were already
attached
- Merging dedupes by media token, clamps to the model cap, and toasts
partial
  adds ("Added 3 of 5 …"), full decks, and unsupported models
- Keyframe-only video models take the first sent image as the start
frame;
reference-capable models switch into reference mode so the deck is
visible
- DRY: shared `galleryItemToRefImage` mapper (reused by the create-image
library picker) and `buildVideoRefsToAdd` (reused by the video-ref
picker,
  duration probing + total-duration cap)
- Bar UX: option list adapts to the selection (video-only hides Create
image),
  icon-only button on phones to avoid overflowing the bar
2026-07-25 15:07:21 +07:00
bflatastic 9fbbfb5877 Webapp: copy paste image references into promptbox 2026-07-25 09:10:01 +07:00
BFlat a49426b77d Webapp/desktop: multi-select batch download on the create-image page (#1806)
- Multi-select and batch download also enabled on the create-image page
(webapp + desktop); zip renamed to artcraft-media-*.zip
2026-07-23 20:37:40 +07:00
BFlat 9f28c525ec Webapp/desktop: batch download video results, refresh credits after failed generations (#1801)
## Changes

**Batch download**
- Add multi-select mode to the create-video results feed (webapp +
desktop) with a "Select" toggle in the topbar, checkboxes on completed
items, and a floating download bar
- Webapp: "Download" bundles selected videos into a single .zip
client-side (new jszip dependency, concurrent fetches, progress and
partial-failure toasts)
- Desktop: "Download" prompts for a folder and saves each selected video
as its own file via native Tauri save, with progress toasts
- Library: add a Download (zip) button to the existing bulk selection
bar
- Shared `ui-generation-list` lib gains a selection store, select
toggle, action bar, and optional selection props on the grid/list views
(existing hosts unaffected)

**Credits refresh on failed generations**
- Webapp: dispatch a credits refresh when the task queue or generation
feed observes a newly failed job (immediately and again after 2.5s so
server-side refunds settle)
- Desktop: also refresh credits when the 5s task poll discovers a
failure that the push event missed, and listen for `credits-change` in
the TopBar

**UI fixes**
- Selection download bar always sits above the promptbox regardless of
its size
- Mobile Generate/History header no longer overlaps the gallery toggles
(flex layout instead of absolute positioning)
- Reference deck top-aligns in the expanded prompt box instead of
centering
- "Play videos with sound" setting now defaults to on
2026-07-23 19:54:12 +07:00
BFlat d4239a3516 Webapp: drag & drop files onto the prompt box to add references (#1800)
- Drop images, videos, or audio anywhere on the prompt box - files are
sorted into the right reference type based on what the current model
supports
- Drag-over overlay shows which file kinds are accepted; turns red when
the hovered files aren't usable
- Video keyframe mode: dropped images fill the start frame, then the end
frame
- Unsupported kinds, full slots, and non-media files get a clear toast
instead of failing silently
- Reuses the existing `useDeckMedia` upload pipelines, so limits and
error handling match the "+" pickers
2026-07-23 03:29:49 +07:00
BFlat 109d7081db Fix: silent failures when submitting a generation (recreate "does nothing" bug) (#1799)
Users reported that after using **Recreate**, pressing the generate
arrow did nothing at all (no error, no card, no spinner). Root cause:
several failure paths gave zero feedback, so a rejected request looked
like a dead button or an outage.

### Changes

- **Show real error messages.** The API layer now keeps the server's
error message (e.g. "Image is required") instead of throwing it away, so
users see why a generation was rejected.
- **Toast on every failed submit.** Image and video pages (webapp +
website) now show an error toast when a generation fails to start.
Previously, most failures were recorded invisibly and never shown.
- **Out of credits feedback on the website.** 402 responses now show a
toast (webapp keeps its upgrade modal).
- **Network errors are announced** instead of failing silently.
- **No more silent submit blocks.** Cases that used to quietly ignore
the click now explain themselves:
  - Model list still loading
- Model needs a starting image (website; also un-disabled the button so
clicking gives feedback)
- **Recreate model fallback is announced.** If the original generation's
model is no longer available, recreate now tells the user it switched to
the default model instead of silently swapping.
2026-07-22 13:27:06 +07:00
BFlat 5faa94a9dd Webapp/desktop: video preview and lightbox sound settings (#1798) 2026-07-22 08:17:19 +07:00
Tanish Manku 9c83c20c46 Frontend: Moodboard persistence (#1793) 2026-07-20 19:08:56 -07:00
Tanish Mankuandbflatastic cb4374ea70 Frontend: video editor persistence (#1792)
Co-authored-by: bflatastic <bombayapirat@gmail.com>
2026-07-20 18:01:24 -07:00
BFlat eb59e7fcd7 Webapp: move audio/3D reference inputs into the prompt box reference deck (#1795)
## Summary

Compacts the legacy reference bands above the prompt box (audio ref row,
multi-view/input-mesh row, reference video row) into the reference deck
design already used on the image and video pages. Mobile forms keep
their existing rows.

## Changes

- **Create Audio** - audio tracks are now deck cards (playable tile,
duration badge) with Upload / From library in the deck's "+" menu; band
removed. Mutual-exclusion and "Audio track required" behavior unchanged.
- **Create 3D World** - guide video is now a deck card; store reworked
to the shared `RefVideo[]` shape.
- **Create 3D Object** - Front/Back/Left/Right are always-visible
labeled slot cards next to the image-ref deck (same design as First/Last
frame on video), plus a "Mesh" slot card for mesh-to-mesh models. Upload
logic deduplicated into `mesh-upload.ts`.
- **Webapp PromptBox** - removed the `mediaReferenceRow` band prop; deck
video/audio groups gate on `videoRefsSupported`/`audioRefsSupported` (no
longer video-page-only); new `referenceSlots` prop; `maxImages: 0`
blocks image uploads on pages whose model takes no image refs.
- **Deck lib** - exported `DeckSlotCard`; new `mesh` card kind;
`sortable: false` for fixed slots; non-finite duration caps hide the
seconds limit; collapsed fan width now tracks card count, so a single
ref sits snug against the prompt (multi-ref unchanged).
2026-07-20 21:12:01 +07:00
BFlat 185bf835a1 Desktop: tags in the gallery modal (#1794)
- Tags now work in the desktop app - no separate build, it all comes
through the shared gallery/lightbox components
- Open any item in the gallery → edit its tags in the details panel
(same chip editor as the webapp, with autocomplete)
- New "Tags" section in the gallery sidebar: your 5 most-used tags + an
"All tags" entry
- Click a tag to see all media with that tag in the normal grid
- "All tags" opens a word cloud (bigger = used more), click a word to
jump in
- Webapp sidebar now also caps at 5 tags with a "See all tags" link
- The tag editor is now one shared component used by both apps instead
of two copies
2026-07-17 20:44:49 +07:00
BFlat 65463ae14c Update character posing video link in README 2026-07-16 22:40:51 +07:00
BFlat fc61c6efd1 Update README.md character pose video 2026-07-16 22:34:47 +07:00
bflatastic 4788ee9a23 Website: character pose video swap 2026-07-16 21:53:00 +07:00
BFlat 77cee77258 Webapp: media tags (#1791)
- Tags editor in LightboxDetails (lightbox + /media/:id): chip input
with autocomplete over the user's existing tags, comma/paste splitting,
duplicate rejection, owner gating via session, and serialized+coalesced
SetMediaFileTags snapshot saves with optimistic chips and error revert
(components/tags/TagChipInput.tsx, TagsSection.tsx)
- Tag browser: "Tags" tab in the library with name/most-used sort, tag
view at /library/tag_… reusing the media grid, breadcrumb header with
rename (FolderNameDialog) and delete (confirm modal), tag context menu
- Sidebar: tags rows under the Assets group below folders, with
expand/collapse chevrons on both Folders (default open) and Tags
(default closed), animated via framer-motion; tag icons use violet to
distinguish them from folders
- New library-tags-store mirroring the folders store (cursor-paged media
caches, optimistic rename/delete with toast revert); shared item mappers
extracted to library-media-map.ts to avoid a circular import; media
deletion now purges tag caches; bulk-selection bar resolves thumbnails
from tag caches too
- Topbar breadcrumbs map /library/tags and tag views to "Tags"
- Tags show as word cloud
2026-07-15 21:57:21 +07:00
BFlat 9f69eb162a Webapp/Desktop: clear all button for promptboxes (#1790) 2026-07-15 19:49:21 +07:00
BFlat 46d05808cc Reference decks + new generate button for image/video promptboxes (#1783)
Redesigns how references are attached in the image and video
promptboxes, on both the desktop app and the webapp (desktop layout -
mobile is unchanged).

### Reference deck
- Replaces the image-toggle icon and the upload bands above the prompt
with a fanned stack of reference cards inside the promptbox, left of the
prompt text
- Hovering the deck expands it to show every attached ref - no
scrolling, it widens toward the end of the promptbox and wraps into more
rows as needed (ready for up to 50 refs for Seedance 2.5)
- Cards support drag-to-reorder (grab/grabbing cursors), hover remove,
name tooltips (Image 1, Video 2, …), upload progress spinners on
individual cards, and click-to-preview fullscreen
- A circular **+** overlaps the deck: hovering shows an add menu grouped
by type (Image / Video / Audio) with compact limits (e.g. `2/9`, `1/3 ·
8/15s`); clicking it opens a file picker that accepts any supported
media and sorts files by type automatically

### Video modes
- Keyframe mode: Start/End frame band replaced with compact "First frame
⇄ Last frame" cards with a swap button
- Reference mode renamed to **Omni reference** and is now the default;
images, videos, and audio all stack into one mixed deck

### Generate button
- Replaced the "Generate" pill with a circular arrow-up button, credit
cost shown beside it

### Under the hood
- New shared deck components live in `@storyteller/ui-promptbox` and are
used by both apps (previously the two promptbox implementations were
fully separate)
- New `GenerateIconButton` in `@storyteller/ui-button`; the old
`GenerateButton` stays for mobile
- Webapp mobile, audio/object/world pages, and the 2D/3D/Edit
promptboxes keep their existing UI
2026-07-14 21:08:15 +07:00
BFlat dbe38a588f Webapp: swap folders and unfoldered sidebar links (#1788) 2026-07-14 20:51:18 +07:00
Tanish Manku 58e2d08c76 Frontend: Endpoint migration and cleanup (#1787) 2026-07-13 22:44:41 -07:00
Tanish Manku f991eafd01 Frontend: mesh/splat classes and handling (#1786) 2026-07-13 22:42:41 -07:00
Tanish Manku 283b4ab04f Frontend: remove dead API client code targeting unregistered endpoints (#1785)
Deletes dormant legacy API surface (zero live callers, verified against
the full backend route table):

- VoiceDesignerApi, VoiceConversionApi, AnalyticsApi
(/v1/analytics/log_session was never registered) - both @storyteller/api
and the Tauri app's ApiManager copies, plus their tests
- PromptsApi/EngineApi: /v1/image_studio/prompt,
/v1/image_studio/session_jobs, /v1/jobs/session/{token} methods (routes
do not exist)
- UserBookmarksApi: broken-path list methods (/list/{user} and
/list/{et}/{etok}; backend shapes are /list/user/{u} and
/list/entity/{et}/{etok}) and the FakeYou-era scoped enums that only
they consumed
- Dashboard UsersApi: RequestPasswordReset/ResetPassword pointing at
removed /v1/request_password_reset + /v1/reset_password (live pages use
PasswordResetApi -> /v1/password_reset/* correctly)

Fixes WeightsApi cover-image path to the registered route:
/v1/weights/weight/{token}/cover_image (both copies).
2026-07-13 21:56:11 -07:00
Brandon Thomas 0003cad179 seedream5 (#1784) 2026-07-13 11:10:21 -04:00
Brandon Thomas 863bd1c4e1 Sort folders (#1782) 2026-07-12 18:13:04 -04:00
Brandon Thomas 79b7e31ec5 unfoldered navigation (frontend) (#1781) 2026-07-12 15:47:28 -04:00
Brandon Thomas eed865e57c cursor behavior fixes (#1780) 2026-07-12 15:28:34 -04:00
Brandon Thomas 348a603cae folderless endpoint (#1779) 2026-07-12 15:23:58 -04:00
Brandon Thomas 9898c6dbcf view meshes and splats in 3d modal (#1778) 2026-07-12 04:34:36 -04:00
Tanish Mankuandbflatastic e0047d9410 3D PageScene priming for scene builder (#1746)
Co-authored-by: bflatastic <bombayapirat@gmail.com>
2026-07-12 03:45:35 -04:00
Brandon Thomas a79ee6ad57 misc fixes for storyteller-web (#1777) 2026-07-12 03:21:23 -04:00
Brandon Thomas 2a9e71f5b8 fix pagination cursors (#1776) 2026-07-11 23:48:34 -04:00
Brandon Thomas 7dffc5fb5c list meshes and splats (#1774) 2026-07-12 09:32:35 +07:00
BFlat bc0174ac7a Webapp: video ref pick from library (#1775)
- The "+" button on the Video Ref row now shows options on hover:
**Upload** or **Pick from library** (on mobile it opens an "Add video"
sheet)
- Picking from library opens the gallery in select mode, filtered to
videos only
- Library videos respect the same limits as uploads (max count and total
duration, with the same error toasts)
- Reused the existing add-button component from the image row so all
reference rows look and behave the same
2026-07-12 09:12:14 +07:00
Brandon Thomas 9cc9d4b9cf Fix cover images (#1773) 2026-07-11 15:32:15 -04:00
Brandon Thomas dc5a4f8dd9 backup for cover images when absent 2026-07-10 17:14:13 -04:00
Brandon Thomas 11ddeb05b5 handle audio results (#1772) 2026-07-10 16:58:34 -04:00
Brandon Thomas 73b40d321e fal 3d fixes (#1771) 2026-07-10 16:41:59 -04:00
Brandon Thomas cb6d0fe27c new tags system (replacing the old one) (#1770) 2026-07-10 05:21:49 -04:00
Brandon Thomas 0914ddc2f5 new tags schemas (#1769) 2026-07-10 03:02:09 -04:00
Brandon Thomas d3585e7734 drop tags tables (we're going to rebuild them) (#1768) 2026-07-10 02:49:40 -04:00
Brandon Thomas 698c330734 remove old tags code (#1767) 2026-07-10 02:31:37 -04:00
1114 changed files with 63162 additions and 9477 deletions
@@ -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 = ?,\n success_inference_execution_millis = ?,\n retry_at = NULL,\n successfully_completed_at = NOW()\nWHERE token = ?\nAND status NOT IN ('complete_success', 'complete_failure')\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "03919ba8286d18980ecaa0038df76f43f5388db0d3838181fa8f15f4cbb932af"
}
@@ -0,0 +1,25 @@
{
"db_name": "MySQL",
"query": "\nSELECT DISTINCT tag_token as `tag_token: TagToken`\nFROM media_file_tags\nWHERE media_file_token = ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "tag_token: TagToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "05033f7f4588cc2d68d9db22236f2be323731ee120217192fa29fe60af776e19"
}
@@ -1,12 +0,0 @@
{
"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 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_generation_provider = ?,\n\n platform_type = ?,\n\n maybe_cover_image_media_file_token = ?,\n\n maybe_mod_user_token = ?,\n is_generated_on_prem = ?,\n generated_by_worker = ?,\n generated_by_cluster = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 41
},
"nullable": []
},
"hash": "1c74e9c2dcc9205e2c9fe60a6de0828208e497e68560246e588092bafd03eb59"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE generic_inference_jobs\nSET\n status = ?,\n assigned_worker = ?,\n last_assigned_worker = ?,\n assigned_cluster = ?,\n first_started_at = NOW()\nWHERE token = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "258ac40d3fab39f110e1e82d50e73542423bfe9a21f8127e6cd83214c1726e25"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "DELETE FROM media_file_tags WHERE tag_token = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "3495f03338e7ee4c2ae9787204d521aefa787312463d392e45d02a693d9afb83"
}
@@ -0,0 +1,245 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.media_class = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_file_id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "media_file_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "maybe_batch_token: BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 12,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL | MULTIPLE_KEY",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 13,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 16,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 17,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 18,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 19,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 20,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 4
},
"nullable": [
false,
false,
false,
false,
true,
false,
true,
true,
true,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "3681e931cc87714d71c95da44d6dd92568ce58aea252164ab3d5f8e96491169c"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users\nFROM user_sessions\nINNER JOIN users\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n users.maybe_feature_flags,\n api_keys.token as `api_key_token: ApiKeyToken`\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"describe": {
"columns": [
{
@@ -72,6 +72,26 @@
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 7,
"name": "maybe_feature_flags",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 8,
"name": "api_key_token: ApiKeyToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
@@ -84,8 +104,10 @@
false,
false,
false,
true
true,
true,
false
]
},
"hash": "c77126adb36f9e2f0248720eb4d8c1cd9d14719f64a02678d8021e26addd2c18"
}
"hash": "62b3e2e058331a6819acd98c634b3bf2d10aa38d43b49ab69fb8637497beec2f"
}
@@ -0,0 +1,36 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`\nFROM generic_inference_jobs\nWHERE job_type = ?\nAND status = 'pending'\nORDER BY id ASC\nLIMIT 1\nFOR UPDATE SKIP LOCKED\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": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true
]
},
"hash": "6ca0fcdf495624cf347f612586868f6dea93f52d9f942a92dcd2b3e6aba7c63c"
}
@@ -0,0 +1,245 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_file_id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "media_file_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "maybe_batch_token: BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 12,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL | MULTIPLE_KEY",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 13,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 16,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 17,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 18,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 19,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 20,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
false,
true,
false,
true,
true,
true,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "6f077c5d35b19319aba4e9f4c59610822334fd304ba9133ba8e099056c426f2a"
}
@@ -0,0 +1,223 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 8,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 9,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 10,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 11,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 12,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 14,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 15,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 16,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 17,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 18,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 4
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true,
true,
false,
false
]
},
"hash": "73540435892b7b4dbc11c6c63712abdb18fd8b3db2bcd56b8518c968b8169de7"
}
@@ -0,0 +1,245 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_file_id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "media_file_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "maybe_batch_token: BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 12,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL | MULTIPLE_KEY",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 13,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 16,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 17,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 18,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 19,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 20,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
false,
true,
false,
true,
true,
true,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "76bcf698f40d852558f071a6909b05b1c960c1cf236831ae009f90de46d3aefb"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE tags\nSET tag_value = ?,\n tag_value_lowercase = ?\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "8938fc564dda214e383a8d30532db6ef964c03f7e1fd6d9561bca5e8b56834cf"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE generic_inference_jobs\nSET\n status = ?,\n frontend_failure_category = ?,\n failure_reason = ?,\n internal_debugging_failure_reason = ?,\n success_execution_millis = ?,\n success_inference_execution_millis = ?\nWHERE token = ?\nAND status NOT IN ('complete_success', 'complete_failure')\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "96599b4854248d84188b092db84edb50dc5402bb7771fd8ea96a96ad43e732c0"
}
@@ -0,0 +1,36 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n maybe_creator_user_token as `maybe_creator_user_token: UserToken`,\n creator_set_visibility as `creator_set_visibility: Visibility`\nFROM media_files\nWHERE token = ?\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nLIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "maybe_creator_user_token: UserToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false
]
},
"hash": "a114fab486689fd2b16197cbe3a3ebb41d3d10937542832e15dd652200b6d419"
}
@@ -0,0 +1,223 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 8,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 9,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 80
}
},
{
"ordinal": 10,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 11,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 12,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 14,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 15,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 16,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 17,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 18,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 4
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true,
true,
false,
false
]
},
"hash": "a52cc2d6e86607c430173553230b3794a4c1ada07274a975c04128e47d3b7f83"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE tags\nSET maybe_deleted_at = NOW(),\n use_count = 0\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "a58fc9050c1fd160e03d6e4932c1b777ea7ab8cbfddca5834faab3e2963d816d"
}
@@ -0,0 +1,245 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_file_id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "media_file_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "maybe_batch_token: BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 12,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL | MULTIPLE_KEY",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 13,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 16,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 17,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 18,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 19,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 20,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
false,
true,
false,
true,
true,
true,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "aa1e693babe6997e75935ec893e6eed46351ec72ce02a9144fd11d2fcfd37fdf"
}
@@ -0,0 +1,69 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n t.id as `id: u64`,\n t.token as `token: TagToken`,\n t.tag_value,\n t.tag_value_lowercase,\n t.use_count as `use_count: u32`\nFROM media_file_tags mft\nJOIN tags t\n ON t.token = mft.tag_token\nWHERE mft.media_file_token = ?\n AND t.maybe_deleted_at IS NULL\nORDER BY t.tag_value_lowercase ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: TagToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "tag_value",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 3,
"name": "tag_value_lowercase",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 4,
"name": "use_count: u32",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "bbfed9e861b1a7cd0cdb2413e2a985aaa79aa9c885408bf5e104d1ea80bdaf49"
}
@@ -0,0 +1,25 @@
{
"db_name": "MySQL",
"query": "\nSELECT token as `token: TagToken`\nFROM tags\nWHERE creator_user_token = ?\n AND tag_value_lowercase = ?\n AND maybe_deleted_at IS NOT NULL\nLIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: TagToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false
]
},
"hash": "cbfe9172d6227175aa6dfd276a4d1a6676d1d1b6bdc3a35927a5d71be38219c0"
}
@@ -0,0 +1,179 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.maybe_origin_filename,\n m.maybe_duration_millis as `maybe_duration_millis: u64`,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_source_job_token = ?\n AND m.maybe_creator_user_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 2,
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 12,
"name": "maybe_duration_millis: u64",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 13,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 14,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
true,
true,
false,
true,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "cd04f4f7b48ae9da53b6a210111f8e3edff94dee30263e22b45d5e29dbe9245a"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n api_keys.token as `api_key_token: ApiKeyToken`\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n users.maybe_feature_flags\nFROM user_sessions\nINNER JOIN users\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"describe": {
"columns": [
{
@@ -75,12 +75,12 @@
},
{
"ordinal": 7,
"name": "api_key_token: ApiKeyToken",
"name": "maybe_feature_flags",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
"max_size": 1020
}
}
],
@@ -95,8 +95,8 @@
false,
false,
true,
false
true
]
},
"hash": "9188e235caa439d50628333c2f4722b9f1a8f575c192d4d36a2039709c412ac0"
}
"hash": "cf2794c87868e77dfaa45bdbe630ae276dbb8c2d041442ba465af0b7a6d61af6"
}
@@ -0,0 +1,25 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n pci.media_token as `media_token: MediaFileToken`\nFROM prompt_context_items pci\nJOIN media_files m\n ON pci.media_token = m.token\nWHERE pci.prompt_token = ?\n AND m.media_class = 'image'\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\nORDER BY pci.id ASC\nLIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "d6dfca49327b2b31078517c5926ae51cc2490b17d8c94a97fefd3decdf8e5664"
}
@@ -0,0 +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 maybe_batch_token = ?,\n maybe_source_job_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_generation_provider = ?,\n\n platform_type = ?,\n\n maybe_cover_image_media_file_token = ?,\n\n maybe_mod_user_token = ?,\n is_generated_on_prem = ?,\n generated_by_worker = ?,\n generated_by_cluster = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 42
},
"nullable": []
},
"hash": "d88098343686ded52a71fc45a1b96d28ea3003975d6e7d20eee2022aa31d8189"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nDELETE FROM tags\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NOT NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "e50d11df0606a12059e13f36daa41fc5aa59d700168425c406e545b535d6d2ba"
}
@@ -0,0 +1,69 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n id as `id: u64`,\n token as `token: TagToken`,\n tag_value,\n tag_value_lowercase,\n use_count as `use_count: u32`\nFROM tags\nWHERE creator_user_token = ?\n AND maybe_deleted_at IS NULL\n AND id < ?\nORDER BY id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: TagToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "tag_value",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 3,
"name": "tag_value_lowercase",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 4,
"name": "use_count: u32",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "eafaf1a9ed2e27dec201ea66d23a65662492ca2dbdac6333629b82defc4b5111"
}
@@ -0,0 +1,69 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n id as `id: u64`,\n token as `token: TagToken`,\n tag_value,\n tag_value_lowercase,\n use_count as `use_count: u32`\nFROM tags\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\nLIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "token: TagToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "tag_value",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 3,
"name": "tag_value_lowercase",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 4,
"name": "use_count: u32",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "ecc3848cd186f1298a2a60c810c69b7a8715719a0c7622122d7e1c5747fab06a"
}
@@ -0,0 +1,124 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n status as `status: enums::common::job_status_plus::JobStatusPlus`,\n job_type as `job_type: enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType`,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n creator_ip_address,\n maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n platform_type as `maybe_platform_type: enums::common::platform_type::PlatformType`,\n on_success_result_entity_token as `maybe_result_media_file_token: tokens::tokens::media_files::MediaFileToken`\nFROM generic_inference_jobs\nWHERE token = ?\nAND job_type IN ('artcraft_minimax_h3_turbo', 'artcraft_minimax_h3_ultra')\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": "job_type: enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"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": 5,
"name": "creator_ip_address",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 160
}
},
{
"ordinal": 6,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 7,
"name": "maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 8,
"name": "maybe_platform_type: enums::common::platform_type::PlatformType",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 9,
"name": "maybe_result_media_file_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
true,
true,
true,
false,
true,
true,
true,
true
]
},
"hash": "f1d41ad17cb01f5aef0ce2eea042ccb3e5e649365a0befaf0f46492b75523442"
}
@@ -0,0 +1,245 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_file_tags mft\nJOIN media_files mf\n ON mf.token = mft.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mft.tag_token = ?\n AND mft.user_token = ?\n AND mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "media_file_id: u64",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
"char_set": 63,
"max_size": 20
}
},
{
"ordinal": 1,
"name": "media_file_token: MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "media_class: MediaFileClass",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 3,
"name": "media_type: MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 4,
"name": "maybe_batch_token: BatchGenerationToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 7,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 8,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 11,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 12,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL | MULTIPLE_KEY",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 13,
"name": "maybe_title",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 15,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 16,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 17,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 18,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 5
}
},
{
"ordinal": 19,
"name": "created_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 20,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 5
},
"nullable": [
false,
false,
false,
false,
true,
false,
true,
true,
true,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "f36c7238231353882b5b2ac39616dcdc6adebf7c7e1198d9c2af4674ba5d982c"
}
Generated
+27 -27
View File
@@ -757,9 +757,9 @@ dependencies = [
"fal_client",
"gmicloud_client",
"grok_api_client",
"kinovi_web_client",
"log",
"reqwest 0.12.23",
"seedance2pro_client",
"serde",
"serde_derive",
"serde_json",
@@ -6359,6 +6359,29 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "kinovi_web_client"
version = "0.0.1"
dependencies = [
"chrono",
"cloudflare_errors",
"env",
"env_logger 0.11.9",
"errors",
"log",
"rand 0.9.2",
"serde",
"serde_derive",
"serde_json",
"test_data",
"test_utils",
"tokio",
"url",
"workspace-build-acceleration",
"wreq",
"wreq-util",
]
[[package]]
name = "kubectl-pod-cleanup"
version = "0.0.1"
@@ -10519,6 +10542,7 @@ dependencies = [
"errors",
"hashing",
"jobs_common",
"kinovi_web_client",
"log",
"mysql_queries",
"pager",
@@ -10526,7 +10550,6 @@ dependencies = [
"reqwest 0.12.23",
"rootly_client",
"rootly_config",
"seedance2pro_client",
"serde",
"serde_derive",
"serde_json",
@@ -10538,29 +10561,6 @@ dependencies = [
"workspace-build-acceleration",
]
[[package]]
name = "seedance2pro_client"
version = "0.0.1"
dependencies = [
"chrono",
"cloudflare_errors",
"env",
"env_logger 0.11.9",
"errors",
"log",
"rand 0.9.2",
"serde",
"serde_derive",
"serde_json",
"test_data",
"test_utils",
"tokio",
"url",
"workspace-build-acceleration",
"wreq",
"wreq-util",
]
[[package]]
name = "selectors"
version = "0.24.0"
@@ -11632,6 +11632,7 @@ dependencies = [
"images",
"infer 0.16.0",
"jwt_signer",
"kinovi_web_client",
"language-tags",
"lexical-sort",
"limitation",
@@ -11663,7 +11664,6 @@ dependencies = [
"rootly_config",
"rust-s3",
"rustls 0.23.36",
"seedance2pro_client",
"serde",
"serde_derive",
"serde_json",
@@ -11867,8 +11867,8 @@ dependencies = [
"easyenv",
"enums",
"errors",
"kinovi_web_client",
"log",
"seedance2pro_client",
"serde_json",
"tokens",
"tokio",
+2 -2
View File
@@ -27,7 +27,7 @@ members = [
"crates/api_clients/openart_client",
"crates/api_clients/rootly_client",
"crates/api_clients/runwayml_client",
"crates/api_clients/seedance2pro_client",
"crates/api_clients/kinovi_web_client",
"crates/api_clients/thumbnail_generator",
"crates/api_clients/worldlabs_api_client",
"crates/api_clients/worldlabs_consumer_client",
@@ -285,7 +285,7 @@ mysql_queries = { path = "crates/schema/database/mysql_queries" }
pager = { path = "crates/lib/pager" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
seedance2pro_client = { path = "crates/api_clients/seedance2pro_client" }
kinovi_web_client = { path = "crates/api_clients/kinovi_web_client" }
server_environment = { path = "crates/lib/server_environment" }
sha1_hash = { path = "crates/lib/sha1_hash" }
shared_service_components = { path = "crates/lib/shared_service_components" }
+1 -1
View File
@@ -28,7 +28,7 @@ Text-to-image is great, but artists *need control*. It's important to know what
| **3D Image Compositing** | ![Video](https://github.com/user-attachments/assets/f93a616f-571d-474e-bcc0-53736de7303d) Use images (backdrops, foreground elements, props, etc.) in scenes with depth and blend them naturally together. Just a couple of images usually leads to great compositions. |
| **2D Image Compositing** | ![Video](https://github.com/user-attachments/assets/d6f99391-e496-4c62-9e37-29734ba5f899) Use images, background removal, layers, and simple drawing tools to precisely compose a scene. |
| **Image to 3D Mesh** | ![Video](https://github.com/user-attachments/assets/600a405c-e360-48c1-9b42-6e657ae6243b) It's almost impossible to lay out complicated objects or block complicated scenes; turning images into 3D helps position elements exactingly and intentionally. |
| **Character Posing** | ![Video](https://github.com/user-attachments/assets/52a8e983-7c8f-42d2-be8b-25296ab9ed57) You can dynamically pose your characters to achieve the precise character, scene, and camera blocking before calling "action". |
| **Character Posing** | ![Video](https://raw.githubusercontent.com/storytold/github-media/22a27373707b13f48fc56405f1f89ca143c43d5d/character-posing.webp) You can dynamically pose your characters to achieve the precise character, scene, and camera blocking before calling "action". |
| **Scene Blocking w/ Kit Bashing** | ![Video](https://github.com/user-attachments/assets/eef025ac-0346-4a46-a023-d48e23629eb5) Use 3D asset kits to precisely block out your scene: get the correct angles, object positions, and rich depth layering you can't with text prompting. |
| **Character Identity Transfer** | ![Video](https://github.com/user-attachments/assets/629119ee-8c76-4a83-9827-8c6c995a3ec1) Use mannequins as simple 3D ControlNets for posing any character. |
| **Background Removal** | ![Video](https://github.com/user-attachments/assets/90c65057-5531-404f-83af-b34e66e24ec1) Remove backgrounds from images to make them useful in 2D or 3D compositing. They can be props, layers, or backdrops. |
@@ -0,0 +1,35 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tag_uses (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- ========== TAG LINK ==========
-- Token of the tag
tag_token VARCHAR(32) NOT NULL,
-- ========== LINKED ENTITY ==========
-- Entity the tag is linked to (composite key)
entity_type VARCHAR(32) NOT NULL,
-- Entity the tag is linked to (composite key)
entity_token VARCHAR(32) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
maybe_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (tag_token, entity_type, entity_token),
KEY index_tag_token (tag_token),
KEY index_entity_type_and_token (entity_type, entity_token),
KEY index_maybe_deleted_at (maybe_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE tag_uses;
@@ -0,0 +1,30 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tags (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- ========== PRIMARY FUNCTIONALITY ==========
-- Effective "primary key" of the beta key.
token VARCHAR(32) NOT NULL,
-- The actual value of the tag.
-- The code will normalize this.
tag_value VARCHAR(255) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
maybe_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (tag_value),
KEY index_maybe_deleted_at (maybe_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE tags;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE IF EXISTS tags;
@@ -0,0 +1,38 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tags (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Effective "primary key".
token VARCHAR(32) NOT NULL,
-- The display value of the tag, as entered by the creator.
tag_value VARCHAR(255) NOT NULL,
-- Lowercased copy of `tag_value` for case-insensitive uniqueness and lookup.
-- (The table collation is utf8mb4_bin, so the code must lowercase this.)
tag_value_lowercase VARCHAR(255) NOT NULL,
-- The user that created the tag.
creator_user_token VARCHAR(32) NOT NULL,
-- Rollup statistic for usage counts.
use_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
maybe_deleted_at TIMESTAMP NULL DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (tag_value_lowercase, creator_user_token),
KEY index_creator_user_token (creator_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE IF EXISTS media_file_tags;
@@ -0,0 +1,32 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE media_file_tags (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- The media file being tagged.
media_file_token VARCHAR(32) NOT NULL,
-- The tag applied to the media file.
tag_token VARCHAR(32) NOT NULL,
-- The user that owns this tagging.
user_token VARCHAR(32) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (media_file_token, tag_token, user_token),
KEY index_media_file_token_and_user_token (media_file_token, user_token),
KEY index_tag_token_and_user_token (tag_token, user_token),
KEY index_media_file_token (media_file_token),
KEY index_tag_token (tag_token),
KEY index_user_token (user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE media_files
DROP INDEX index_maybe_source_job_token,
DROP COLUMN maybe_source_job_token;
@@ -0,0 +1,13 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- The inference job ('jinf_...') whose completion produced this media file.
-- Only set for files written as job outputs; uploads and other origins stay NULL.
ALTER TABLE media_files
ADD COLUMN maybe_source_job_token VARCHAR(32) DEFAULT NULL;
-- For "outputs of this job"-style lookups.
-- The column is all-NULL at creation time, so the index build is cheap.
ALTER TABLE media_files
ADD INDEX index_maybe_source_job_token (maybe_source_job_token);
@@ -0,0 +1,36 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- N:M mapping between `media_files` and `tags`, scoped to the owning user.
CREATE TABLE media_file_tags (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- The media file being tagged.
media_file_token VARCHAR(32) NOT NULL,
-- The tag applied to the media file.
tag_token VARCHAR(32) NOT NULL,
-- The user that owns this tagging.
user_token VARCHAR(32) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (media_file_token, tag_token, user_token),
KEY index_media_file_token_and_user_token (media_file_token, user_token),
KEY index_tag_token_and_user_token (tag_token, user_token),
KEY index_media_file_token (media_file_token),
KEY index_tag_token (tag_token),
KEY index_user_token (user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -102,6 +102,10 @@ CREATE TABLE media_files (
-- If the media file is generated as part of a batch, this designates the batch.
maybe_batch_token VARCHAR(32) DEFAULT NULL,
-- The inference job ('jinf_...') whose completion produced this media file.
-- Only set for files written as job outputs; uploads and other origins stay NULL.
maybe_source_job_token VARCHAR(32) DEFAULT NULL,
-- ========== METADATA ==========
-- If the file is a user upload (not including studio renders).
@@ -327,6 +331,7 @@ CREATE TABLE media_files (
KEY fk_maybe_origin_model_token (maybe_origin_model_token),
KEY fk_maybe_origin_model_type_and_token (maybe_origin_model_type, maybe_origin_model_token),
KEY index_maybe_batch_token (maybe_batch_token),
KEY index_maybe_source_job_token (maybe_source_job_token),
KEY index_is_user_upload (is_user_upload),
KEY index_is_intermediate_system_file (is_intermediate_system_file),
KEY index_maybe_scene_source_media_file_token (maybe_scene_source_media_file_token),
@@ -0,0 +1,44 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- User-created tags for media files. Tags are scoped per-user: two users can
-- each have their own tag with the same value. Applied to media files via the
-- `media_file_tags` join table.
CREATE TABLE tags (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Effective "primary key".
token VARCHAR(32) NOT NULL,
-- The display value of the tag, as entered by the creator.
tag_value VARCHAR(255) NOT NULL,
-- Lowercased copy of `tag_value` for case-insensitive uniqueness and lookup.
-- (The table collation is utf8mb4_bin, so the code must lowercase this.)
tag_value_lowercase VARCHAR(255) NOT NULL,
-- The user that created the tag.
creator_user_token VARCHAR(32) NOT NULL,
-- Rollup statistic for usage counts.
use_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
maybe_deleted_at TIMESTAMP NULL DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (tag_value_lowercase, creator_user_token),
KEY index_creator_user_token (creator_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+227
View File
@@ -0,0 +1,227 @@
# PageScene Animations — Branch Handoff
Branch: `frontend/pagescene-animations` (forked from `main` @ `6329045350`, 2026-08-04).
Everything below is TypeScript-typechecked per-lib (`tsc -p <lib>/tsconfig.lib.json`); runtime
verification status is tracked in the checklist at the bottom. Drag-and-drop has had one manual
pass (confirmed working); most other surfaces have not been launched.
## What this branch does
Makes skeletal animations a first-class asset in the 3D editor (pagescene): browse them in the
asset drawer, drag them onto characters or the timeline with live rig-compatibility feedback,
play clips baked into uploaded models, preview animations everywhere models are previewed, and
upload Mixamo FBX files (with or without mesh) end to end.
## Commit map
| Commit | What |
|--------------|-------------------------------------------------------------------------|
| `7af3fb9856` | Animations tab in AssetModal; AnimationsDrawer retired |
| `1fcbc55780` | Pointer drag-and-drop with live rig-compatibility badge |
| `79d7ade8bc` | Baked-in clips on the timeline (+ clip-row eligibility widened) |
| `142279414e` | Animation dropdown in Viewer3D + upload preview |
| `913905308e` | Clip fixes: vanishing character, 1 s strips, select-to-delete, undo |
| `60251104e3` | FBX accepted at the picker, converted to GLB in-browser |
| `0942ec4804` | Nx tsconfig references for the new ui-select dependency |
| `d0c7b65a6e` | FBX conversion moved into a Web Worker (main-thread fallback) |
| `8e51b6c524` | Explicit "Upload as Animation" toggle + required duration_millis |
| `00ebeae10c` | User's uploaded animations merged into the Animations tab |
| `e1c7a0c7b0` | Persistent skeleton-helper toggle in the outliner |
| `30f40c0ddb` | Previewer bone toggles + mesh-less framing/mixer-binding fixes |
| `5f7ba7bbc5` | AnimationsModal (All/Presets/Uploaded) + timeline quality-of-life |
| `d55735620a` | Timeline ruler tick marks + strip button spacing |
| `79875d8c76` | Upload animations from the AnimationsModal (initialCategory) |
| *(this)* | NodeHierarchyHelper: bone toggle works for converted mesh-less GLBs |
## Features
### 1. Animations tab in the asset drawer (`AssetModal`)
- New left-rail section fed by `GET /v1/media_files/list_featured?filter_engine_categories=animation`
(GLB/GLTF only — the clip loader is GLTF-based). Featured results REPLACE the 37 hardcoded
`demoAnimationItems`; demos are only the empty-API fallback.
- The session user's own uploaded animations (`list/user`, same filter) render AHEAD of the
featured set. Fetched on every modal open and after each upload; fails silently for anonymous
users (`suppressErrorToast` on the user-media fetch path).
- Card click appends the clip to the selected character's row at the earliest free slot (toast
when no character is selected or the row is full). The old right-docked `AnimationsDrawer` is
deleted; the old HTML5-DnD path (`ANIMATION_CLIP_MIME`) is gone with it.
### 2. Drag-and-drop with rig-compatibility badge (`DndAsset`)
- Animation cards ride the same pointer-drag pipeline as every other asset. On drag start the
clip's track node names are preloaded (`loadRawGlb`, cached per media id); hovering a
character (or any skinned object) checks those names against its nodes — the exact direct-bind
rule `CharacterAnimationManager` plays by. No retargeting (explicitly out of scope).
- The shared `DragGhost` shows the verdict: green check (binds) / red slash (no target or rig
mismatch); nothing while the clip is still loading. Incompatible or slot-less drops are
rejected with a toast.
- Drop targets: a character in the 3D scene (earliest free slot) or a timeline row
(`data-clip-drop-uuid`; pointer-x → time via the `data-timeline-ruler` rect).
### 3. Timeline clips: behavior + UX
- **Strips default to 1 s** and the strip width is the authoritative play window
(`LaneRuntime.stripDuration`): trimming genuinely cuts playback; loop repeats inside the
strip; a non-loop strip wider than its clip freezes on the last frame. On load a strip only
shrinks (clip shorter than default) — never grows.
- **Rest-pose in gaps**: `Skeleton.pose()` was replaced with a rest-pose snapshot captured at
mixer creation (all nodes BELOW the object root; root excluded so gizmo/keyframes never snap
back). `pose()` rebuilds bone locals from inverse bind matrices and mis-scales cm-rigs — that
was the "character vanishes when moving a strip" bug.
- **Selection UX**: strips are click-to-select; × renders only on the selected strip;
Del/Backspace deletes; removal is individually undoable (`RemoveClipLaneAction` — the one
exception to clip edits riding the timeline Save/Cancel session).
- **Full rows reject adds**: `resolveFreeStart` returns null instead of accepting an
overlapping clamp.
### 4. Baked-in clips
- `ClipStrip.bakedClipIndex` marks a strip sourced from the object's own `animations[]`
(resolved synchronously — no media fetch). Objects with baked clips get a timeline clip row
with a "+" picker listing them.
- Clip-row eligibility is wider than characters: any skinned object (creatures, rigged uploads)
accepts animation drags (bind check gates); baked-only objects get a row for the picker.
- INVARIANT: baked clips are never removed from the THREE model. Removing a baked strip only
unschedules it; the clip stays on `object.animations` and in the picker. Library strips stay
fully removable.
- Outliner metadata (`hasSkeleton`, `bakedClips`) is computed in `convert_object` and refreshed
after every GLB load (`SceneDeps.onGlbLoaded` → `refreshOutliner`).
### 5. Previewers (Viewer3D + upload preview)
- Both play baked clips: autoplay the first, dropdown with every clip + "T-pose (none)", hidden
entirely for animation-less models.
- Both have a bone icon toggling a skeleton overlay — default ON when the model has no mesh
(Mixamo "without skin" exports), so animation-only files are visible. The upload thumbnail
snapshot then captures the skeleton render.
- The overlay adapts to the rig kind: real `THREE.Bone` joints get `THREE.SkeletonHelper`;
mesh-less models whose joints re-imported as plain nodes get `NodeHierarchyHelper`
(exported from `viewer-3d`) — GLTF only round-trips bone-ness through a *skin*, and a
mesh-less export has no skin, so converted FBX animations lose `Bone`-ness entirely. The
toggle shows for `hasBones || !hasMesh`.
- Latent bugs fixed: upload preview discarded `gltf.animations`; its child re-parenting loop
skipped every other child (`scene.add` mutates the array mid-forEach); its mixer bound before
the model entered the scene (silently played nothing); camera framing NaN'd on empty
bounding boxes (now falls back to bone world positions); camera now fits once, not per-child.
### 6. FBX upload support
- The 3D uploader accepts `.fbx` and normalizes it at the door: `FBXLoader` → `GLTFExporter`
→ binary GLB with clips preserved. Runs in a dedicated Web Worker (transferred buffers, jobs
naturally serialized); a broken worker environment falls back to the main thread. The raw FBX
can never reach the backend — retry of a failed conversion re-converts.
- Caveats: embedded textures may not survive; units are exported as parsed (cm-scaled Mixamo
rigs are not rescaled).
### 7. Upload as Animation
- Mutually exclusive checkbox next to "Upload as Character" → `engine_category=animation`.
Mesh-less files preselect it (preview loader reports `hasMesh:false`); a user click always
wins. Rig-type dropdown defaults to Mixamo (characters keep Mixamo ArKit first).
- The backend-required `maybe_duration_millis` is read from the longest clip at submit time;
clip-less files are flagged and excluded client-side.
### 8. Outliner skeleton toggle (`SkeletonHelperController`)
- Bone icon on skinned rows toggles a persistent `SkeletonHelper` on LAYER 1 (editor-only —
the render camera renders layer 0, so skeletons never appear in captures/recordings).
- Persists as `userData.skeletonVisible` through scene save/load (explicit restore in the proxy
load path). Reconciliation rides `OutlinerRefreshedEvent` — add/delete/load/new-scene and the
post-GLB-load refresh all funnel there.
- FK/pose-mode compatible: `PoseModeChangedEvent("pose")` suppresses the persistent helper for
the posed character (FKHelper draws its own rig); exit restores it. The toggle state itself
is never touched.
## Manual test checklist (outstanding)
- [ ] Prod data: does `list_featured?filter_engine_categories=animation` return rows? If yes
they replace the demo clips in the tab; if empty, the 37 demos show.
- [ ] Clip→rig bind smoke test on shipped characters (runtime-unverified `mixamorig:*`
assumption; a "bound 0 tracks" console warning means retargeting becomes the real task).
- [ ] Creature experiment: drag a clip onto a creature — badge should reflect bind result.
- [ ] Baked clips: "+" picker on an animated object's row; add/remove/re-add; save/reload
persistence (the `object.animations` stash on load is gated on `auto_add` — statically
unconfirmed for every load path).
- [ ] Clip UX round: strips at 1 s; trim semantics; select → ×/Del; undo/redo of removal;
character stays visible when a strip moves off the playhead.
- [ ] FBX round trip: pick `.fbx` → converts (worker; check console for worker load failures —
falls back to main thread) → skeleton visible + clip dropdown → Upload as Animation
preselected → appears at front of Animations tab → drag onto character → plays.
- [ ] Converted-FBX scale: if placed objects come in ~100× too big, the converter needs a
normalize-scale step.
- [ ] Outliner bone toggle: on/off, FK-mode enter/exit restore, save/reload persistence, and
absence from Capture/Record output.
- [ ] Previewer dropdown + bone toggle across lightbox, media page, lightbox-modal,
ImageTo3DExperience, upload modal.
## Debug pass + hardening round (post-review)
A four-agent review of the combined branch produced 30 findings (5 high / 12 medium / 13 low);
ALL have been fixed or explicitly closed, one commit per finding (range `72cb218e..b21732d1`).
Highlights, roughly in commit order:
- Highs: baked clips cloned per lane (shared-AnimationAction playback/removal corruption);
runtime revalidation for objects recreated under the same uuid (delete→undo, reloads) — later
hardened into LOOP-FREE terminal lane states (bound / missing / unbindable, reassessed only on
root-instance change; the pre-fetch rig gate is sound because every load path stamps the target
uuid only post-load); skeleton-helper sync self-heals across in-session reloads; upload modal
can no longer report success for empty/partial submissions; ref-guarded double-submit.
- Mediums: strip removal mirrored into the Save/Cancel `saved` snapshot (Cancel can't resurrect,
no phantom undo); stale strip/keyframe selections pruned in the bridge + live-checked before
swallowing Del/Backspace; one winning lane per character at flush boundaries (blend-glitch
frame was being ENCODED into recordings); orphaned tracks/lanes filtered from scene JSON at
save; per-clip queued-drop tokens; animation raycasts gated to the canvas element + honest
front-most-surface occlusion; virtualized-grid thumbnail state keyed by item; one shared
lazily-fetched animation-library store (was double-fetch + double-toast + divergent lists);
previewer rest-pose snapshots replace `Skeleton.pose()` everywhere; all-node camera-framing
fallback for plain-node rigs; upload-preview races (superseded-load cancellation, snapshot
after helper creation, File-identity row statuses, retry re-entry).
- Lows: duration-shrink re-fits strips; baked-clip picker portals out of the scrolling row list;
timeline resets on scene switch/new scene instead of leaking across; click-to-add accepts every
drag-eligible target; queued drops bail on a torn-down editor (`Editor.isLive`); top-level root
lookups unified; flick-click race killed via `wasDragGesture`; converting-overlay control reset;
conversion completions reload only the previewed entry; worker `onmessageerror` recovery +
corrupt-vs-clip-less error messages + explicit `initialCategory` wins over mesh-less
auto-detect; Uploaded tab pages past the 100-item cap.
- Closed without code: the "compat cache stales on streaming characters" finding (invalidated —
uuid-resolvable roots are always fully loaded) and the Tauri `promptSignup` gap (desktop is
always signed in by adapter contract).
## Landed since the original handoff
- **AnimationsModal** (was deferred — now built, `5f7ba7bbc5`/`79875d8c76`): standalone modal
with All / Presets / Uploaded tabs, opened from an "Add Animation" button next to Enter Pose
Mode and from the "+" add-asset menu. Adding a clip expands the timeline and scrolls to the
character (`timelineRevealObjectUuid`); the modal closes after an add unless "Reopen after
adding" is on. Its "Upload animation" button opens `UploadModal3D` with
`initialCategory="animation"` (anonymous users get the signup prompt), landing on the
Uploaded tab afterwards. `AssetModal`'s animations tab was folded into this.
- **Timeline QoL**: ruler tick marks (major/second/sub-tick), selected-object-only track
filter, directional expand/collapse chevrons, thumbnail-less animation cards get an icon
placeholder.
## Deferred (explicitly out of scope for this branch)
- **Split-screen preview window** (based on the `?output=` `DemoOutputOverlay`) — parked until
explicit approval.
- **Retargeting** (`SkeletonUtils.retargetClip`) — too complex for now; mismatched rigs are
simply rejected by the bind check.
- **Clip blending/layering** — one non-overlapping row per object, by design.
## Key files
| Area | Files |
|---------------------|-------------------------------------------------------------------------------------------|
| Drawer tab | `pagescene/src/lib/comps/AssetMenu/AssetModal.tsx`, `shared/ItemElement.tsx` |
| Drag-and-drop | `pagescene/src/lib/DragAndDrop/DndAsset.ts`, `gallery-modal/src/lib/DragGhost.tsx` |
| Timeline data/engine| `pagescene/src/lib/engine/timeline/types.ts`, `engine/editor/TimelineController.ts`, |
| | `engine/animation/CharacterAnimationManager.ts`, `engine/editor/actions/RemoveClipLaneAction.ts` |
| Timeline UI | `pagescene/src/lib/comps/Timeline/TimelineEditor.tsx`, `TimelineClipRow.tsx` |
| Skeleton toggle | `pagescene/src/lib/engine/editor/SkeletonHelperController.ts`, `comps/Outliner/Outliner.tsx` |
| Previewers | `viewer-3d/src/lib/viewer-3d.tsx`, `upload-modal/.../utilities/loadPreviewOnCanvas.ts` |
| FBX + upload | `upload-modal/.../utilities/convertFbxToGlb.ts` (+ `.worker.ts`), `UploadModal3D.tsx`, |
| | `UploadFiles3D.tsx`, `utilities/readGlbAnimationDuration.ts` |
The pagescene `CLAUDE.md` "Mixamo animation clips" section has been kept current with these
changes and carries the same invariants in engine-doc form.
+2 -2
View File
@@ -51,9 +51,9 @@ helpers go in the parent `mod tests` so sub-modules can `use super::*`.
- `storyteller_web` — main HTTP API monolith (actix-web)
- `artcraft` (desktop) — Tauri desktop app
- `artcraft_router` — provider routing for image/video generation (Artcraft, Fal, Seedance2Pro, Muapi)
- `artcraft_router` — provider routing for image/video generation (Artcraft, Fal, KinoviWeb, Muapi)
- `artcraft_api_defs` — shared HTTP API type definitions
- `seedance2pro_client` — HTTP client for the Kinovi/Seedance2Pro video generation service
- `kinovi_web_client` — HTTP client for the Kinovi/KinoviWeb video generation service
- `enums` — database-backed enums stored as VARCHAR fields
- `tokens` — primary key identifiers with Stripe-like prefixes (e.g. `user_`, `mf_`)
- `mysql_queries` — SQLx MySQL queries for the backend
@@ -1,6 +1,7 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use enums::by_table::media_files::media_file_class::MediaFileClass;
use tokens::tokens::folders::FolderToken;
use tokens::tokens::media_files::MediaFileToken;
@@ -22,6 +23,28 @@ pub struct ListFolderMediaFilesQueryParams {
pub limit: Option<u32>,
}
// ── GET /v1/folders/media_files_without_folder ──
//
// NB: `ListMediaFilesWithoutFolderSuccessResponse` and its list item live in
// `storyteller_web`'s handler because the wire shape embeds
// `MediaFileCoverImageDetails` and `MediaLinks` constructors that depend
// on the request's `MediaDomain` + `ServerEnvironment`.
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListMediaFilesWithoutFolderQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
/// Optional filter on the coarse media class.
///
/// Usage:
/// - `?filter_media_class=image`
/// - `?filter_media_class=video`
/// - `?filter_media_class=mesh`
/// - `?filter_media_class=splat`
pub filter_media_class: Option<MediaFileClass>,
}
// ── PUT /v1/folders/media_files/{folder_token}/bulk_add ──
#[derive(Deserialize, ToSchema)]
@@ -0,0 +1,29 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory;
/// Request body for `POST /v1/internal/minimax_jobs/job/{job_token}/failure`.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct MarkMinimaxJobFailureRequest {
/// Optional failure class the frontend can localize and present.
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
/// Optional failure reason we can show the user (512 characters max).
pub maybe_user_failure_reason: Option<String>,
/// Optional internal-only stack trace or error (512 characters max).
pub maybe_internal_debugging_failure_reason: Option<String>,
/// Optional total wall-clock runtime of the failed attempt, in milliseconds.
pub execution_duration_millis: Option<u64>,
/// Optional inference-only runtime of the failed attempt, in milliseconds.
pub inference_duration_millis: Option<u64>,
}
/// Response body for `POST /v1/internal/minimax_jobs/job/{job_token}/failure`.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct MarkMinimaxJobFailureResponse {
pub success: bool,
}
@@ -0,0 +1,29 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
// NB: The success request is form-multipart (video bytes + optional
// `duration_millis` / `width` / `height` metadata fields), so its form struct
// lives with the actix handler; only the response shape is declared here.
/// Response body for `POST /v1/internal/minimax_jobs/job/{job_token}/success`.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct MarkMinimaxJobSuccessResponse {
pub success: bool,
/// The media file created from the uploaded video.
pub media_file_token: MediaFileToken,
/// Video duration stored on the media file record, in milliseconds
/// (worker-supplied, or probed from the file when not supplied).
pub maybe_duration_millis: Option<u64>,
/// Frame width stored on the media file record, in pixels
/// (worker-supplied, or probed from the file when not supplied).
pub maybe_width: Option<u32>,
/// Frame height stored on the media file record, in pixels
/// (worker-supplied, or probed from the file when not supplied).
pub maybe_height: Option<u32>,
}
@@ -0,0 +1,33 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
/// The first-party Minimax models a worker can request jobs for.
///
/// This is deliberately its own enum (not `CommonVideoModel`) so the internal
/// worker API surface stays small and stable.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ToSchema)]
pub enum MinimaxWorkerModel {
#[serde(rename = "minimax_h3_turbo")]
MinimaxH3Turbo,
#[serde(rename = "minimax_h3_ultra")]
MinimaxH3Ultra,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_round_trip() {
let cases = [
(MinimaxWorkerModel::MinimaxH3Turbo, "\"minimax_h3_turbo\""),
(MinimaxWorkerModel::MinimaxH3Ultra, "\"minimax_h3_ultra\""),
];
for (variant, json) in cases {
assert_eq!(serde_json::to_string(&variant).unwrap(), json);
let parsed: MinimaxWorkerModel = serde_json::from_str(json).unwrap();
assert_eq!(parsed, variant);
}
}
}
@@ -0,0 +1,4 @@
pub mod mark_minimax_job_failure;
pub mod mark_minimax_job_success;
pub mod minimax_worker_model;
pub mod obtain_minimax_job;
@@ -0,0 +1,85 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_bitrate::CommonBitrate;
use enums::common::generation::common_generation_mode::CommonGenerationMode;
use enums::common::generation::common_resolution::CommonResolution;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use crate::common::responses::media_links::MediaLinks;
use crate::internal::minimax_jobs::minimax_worker_model::MinimaxWorkerModel;
/// Request body for `POST /v1/internal/minimax_jobs/obtain_job`.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct ObtainMinimaxJobRequest {
/// Which model the worker wants a job for.
pub model: MinimaxWorkerModel,
/// The worker's hostname (linux hostname, k8s pod name), if reported.
pub worker_hostname: Option<String>,
/// The cluster the worker runs in (e.g. "runpod", "lambdalabs"), if reported.
pub cluster_name: Option<String>,
}
/// Response body for `POST /v1/internal/minimax_jobs/obtain_job`.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct ObtainMinimaxJobResponse {
pub success: bool,
/// The obtained job, or `None` when no pending job is available.
pub maybe_job: Option<ObtainedMinimaxJob>,
}
/// A job the worker now holds a lock on (status was moved to `started`).
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct ObtainedMinimaxJob {
pub job_token: InferenceJobToken,
/// Echo of the requested model.
pub model: MinimaxWorkerModel,
/// The prompt and inputs the worker needs to run inference. Only `None`
/// when the job was somehow enqueued without a prompt record (or the
/// prompt lookup failed) — workers should fail such jobs via the failure
/// endpoint.
pub maybe_prompt: Option<MinimaxJobPromptDetails>,
}
/// The prompt (text + generation settings) and its attached media references.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct MinimaxJobPromptDetails {
pub prompt_token: PromptToken,
pub maybe_positive_prompt: Option<String>,
pub maybe_negative_prompt: Option<String>,
pub maybe_generation_mode: Option<CommonGenerationMode>,
pub maybe_aspect_ratio: Option<CommonAspectRatio>,
pub maybe_resolution: Option<CommonResolution>,
pub maybe_bitrate: Option<CommonBitrate>,
pub maybe_generate_audio: Option<bool>,
pub maybe_duration_seconds: Option<u32>,
/// Image / video / audio references attached to the prompt, with full CDN
/// links, in their original order.
pub media_references: Vec<MinimaxJobMediaReference>,
}
/// One media input attached to the prompt (start frame, end frame, or an
/// image / video / audio reference), with full CDN links.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct MinimaxJobMediaReference {
pub media_file_token: MediaFileToken,
/// The role the media plays (start frame, end frame, image / video / audio
/// reference, etc.).
pub semantic_type: PromptContextSemanticType,
/// Full CDN links to the media file.
pub media_links: MediaLinks,
}
@@ -0,0 +1,4 @@
//! Internal-facing API surfaces (worker fleets, etc.). These are
//! authenticated with internal API keys, never consumer credentials.
pub mod minimax_jobs;
@@ -5,6 +5,7 @@ pub mod common;
pub mod credits;
pub mod folders;
pub mod generate;
pub mod internal;
pub mod jobs;
pub mod media_file;
pub mod moderation;
@@ -13,6 +14,7 @@ pub mod omni_gen;
pub mod prompts;
pub mod stripe_artcraft;
pub mod subscriptions;
pub mod tags;
pub mod user_referral_codes;
pub mod users;
pub mod utils;
@@ -0,0 +1,61 @@
//! Types for `GET /v1/media_files/by_job/{job_token}` — list the media files
//! produced by an inference job.
use chrono::{DateTime, Utc};
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_type::MediaFileType;
use tokens::tokens::batch_generations::BatchGenerationToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use crate::common::responses::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::common::responses::media_links::MediaLinks;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ListMediaFilesByJobSuccessResponse {
pub success: bool,
/// The job's output files, oldest first.
pub media_files: Vec<JobMediaFileInfo>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct JobMediaFileInfo {
pub token: MediaFileToken,
/// The coarse-grained class of media file: image, video, etc.
pub media_class: MediaFileClass,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
/// This is closer in meaning to a "mime type".
pub media_type: MediaFileType,
/// If the file was generated as part of a batch, this is the token for the batch.
pub maybe_batch_token: Option<BatchGenerationToken>,
/// The foreign key to the prompt used to generate the media, if applicable.
pub maybe_prompt_token: Option<PromptToken>,
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub media_links: MediaLinks,
/// Information about the cover image. Many media files do not require a cover image,
/// e.g. image files, video files with thumbnails, audio files, etc.
/// 3D files require them.
pub cover_image: MediaFileCoverImageDetails,
/// The original filename for uploaded files, if they were provided.
/// In the future we'll provide our own internal optional filenames.
pub maybe_original_filename: Option<String>,
/// Duration for audio and video files, if available.
/// Measured in milliseconds.
pub maybe_duration_millis: Option<u64>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1 @@
pub mod list_media_files_by_job;
@@ -0,0 +1,64 @@
//! Shared types for the by-class session media file list endpoints
//! (`/v1/media_files/mesh/list`, `/v1/media_files/splat/list`).
use chrono::{DateTime, Utc};
use serde_derive::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::visibility::Visibility;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use crate::common::responses::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::common::responses::media_links::MediaLinks;
use crate::common::responses::user_details_light::UserDetailsLight;
/// Query string parameters for the by-class session media file lists.
/// Pagination only — the media class is fixed by the endpoint.
#[derive(Deserialize, IntoParams)]
pub struct ListSessionMediaFilesByTypeQueryParams {
pub sort_ascending: Option<bool>,
pub page_size: Option<usize>,
pub cursor: Option<String>,
pub cursor_is_reversed: Option<bool>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct SessionMediaFileInfo {
/// Primary key identifier
pub token: MediaFileToken,
/// The coarse-grained class of media file.
/// Fixed per endpoint (`mesh` or `splat`).
pub media_class: MediaFileClass,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
/// This is closer in meaning to a "mime type".
pub media_type: MediaFileType,
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub media_links: MediaLinks,
/// Information about the cover image. Many media files do not require a cover image,
/// e.g. image files, video files with thumbnails, audio files, etc.
/// 3D files require them.
pub cover_image: MediaFileCoverImageDetails,
/// User info
pub maybe_creator_user: Option<UserDetailsLight>,
pub creator_set_visibility: Visibility,
/// The generation's prompt record, when there is one. Clients resolve it
/// to display the original prompt and model.
pub maybe_prompt_token: Option<PromptToken>,
/// The name or title of the media file (optional)
pub maybe_title: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1,14 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::common::responses::pagination_cursors::PaginationCursors;
use crate::media_file::list::by_type::list_session_common::SessionMediaFileInfo;
pub const LIST_SESSION_MESH_MEDIA_FILES_URL_PATH: &str = "/v1/media_files/mesh/list";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ListSessionMeshMediaFilesSuccessResponse {
pub success: bool,
pub results: Vec<SessionMediaFileInfo>,
pub pagination: PaginationCursors,
}
@@ -0,0 +1,14 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::common::responses::pagination_cursors::PaginationCursors;
use crate::media_file::list::by_type::list_session_common::SessionMediaFileInfo;
pub const LIST_SESSION_SPLAT_MEDIA_FILES_URL_PATH: &str = "/v1/media_files/splat/list";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ListSessionSplatMediaFilesSuccessResponse {
pub success: bool,
pub results: Vec<SessionMediaFileInfo>,
pub pagination: PaginationCursors,
}
@@ -0,0 +1,3 @@
pub mod list_session_common;
pub mod list_session_mesh_media_files;
pub mod list_session_splat_media_files;
@@ -0,0 +1 @@
pub mod by_type;
@@ -1,4 +1,6 @@
pub mod delete_media_file;
pub mod job;
pub mod list;
pub mod list_batch_generated_media_files;
pub mod list_session_project_media_files;
pub mod project;
pub mod project;
@@ -69,4 +69,23 @@ pub struct OmniGenVideoCostAndGenerateRequest {
/// Some models will default this to true, others will default it to false,
/// so it's best to be explicit.
pub generate_audio: Option<bool>,
/// Frontend-supplied hints for cost estimation only. IGNORED by
/// generation — the generate endpoints measure inputs themselves (e.g.
/// ffprobing reference videos) and bill from those measurements.
pub estimate_only: Option<EstimateFields>,
}
/// These fields are ignored by generation. They're only used by the frontend
/// to aid in cost estimation (e.g. Seedance 2.5 bills reference-video input
/// seconds, so the cost quote needs the combined input duration without the
/// server downloading and probing the files on every poll).
#[derive(Clone, Copy, Serialize, Deserialize, ToSchema, Debug, Default)]
pub struct EstimateFields {
/// Combined duration of all reference video inputs, in milliseconds.
pub total_input_video_duration_millis: Option<u32>,
/// Combined duration of all reference audio inputs, in milliseconds.
/// (No model bills audio input seconds yet; accepted for future models.)
pub total_input_audio_duration_millis: Option<u32>,
}
@@ -11,7 +11,7 @@ pub struct OmniGenVideoGenerateResponse {
/// All job tokens created by this request (including the primary).
/// For single-job requests this will contain just one element matching
/// `inference_job_token`. For batch requests (eg. Seedance2Pro with
/// `inference_job_token`. For batch requests (eg. KinoviWeb with
/// multiple order IDs) this will contain all of them.
pub all_job_tokens: Vec<InferenceJobToken>,
}
@@ -0,0 +1,35 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── POST /v1/tags/media_file/add/{media_file_token} ──
#[derive(Deserialize, ToSchema)]
pub struct AddMediaFileTagsPathInfo {
pub media_file_token: MediaFileToken,
}
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
/// both or neither). Tag text is trimmed and deduped case-insensitively;
/// empty entries are dropped. Must sanitize to at least one tag (400
/// otherwise).
#[derive(Deserialize, ToSchema)]
pub struct AddMediaFileTagsRequest {
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
pub maybe_tags: Option<String>,
/// Tags as a list. Entries are still trimmed.
pub maybe_tags_list: Option<Vec<String>>,
}
#[derive(Serialize, ToSchema)]
pub struct AddMediaFileTagsSuccessResponse {
pub success: bool,
/// The tags from this request after upsert, with canonical tokens and
/// fresh use counts. Tags already on the file are absorbed (no error).
pub tags: Vec<TagDetails>,
}
@@ -0,0 +1,38 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── POST /v1/tags/bulk_add ──
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
/// both or neither). Tag text is trimmed and deduped case-insensitively;
/// empty entries are dropped. Must sanitize to at least one tag (400
/// otherwise). Media file tokens are deduped; tokens the user doesn't
/// own (or that are deleted) are silently skipped.
#[derive(Deserialize, ToSchema)]
pub struct BulkAddTagsRequest {
/// Media files to tag.
pub media_file_tokens: Vec<MediaFileToken>,
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
pub maybe_tags: Option<String>,
/// Tags as a list. Entries are still trimmed.
pub maybe_tags_list: Option<Vec<String>>,
}
#[derive(Serialize, ToSchema)]
pub struct BulkAddTagsSuccessResponse {
pub success: bool,
/// The subset of the input tokens that were actually tagged: files
/// that exist, aren't deleted, and are owned by the caller.
pub accepted_media_file_tokens: Vec<MediaFileToken>,
/// The tags from this request after upsert, with canonical tokens and
/// fresh use counts.
pub tags: Vec<TagDetails>,
}
@@ -0,0 +1,33 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── POST /v1/tags/media_files/bulk_list_tags ──
//
// POST rather than GET: the token list belongs in a request body, and
// GET request bodies are non-standard. The operation is still a pure
// read.
#[derive(Deserialize, ToSchema)]
pub struct BulkListMediaFileTagsRequest {
/// Media files to look up. Tokens with no tags come back with an
/// empty `tags` list.
pub media_file_tokens: Vec<MediaFileToken>,
}
#[derive(Serialize, ToSchema)]
pub struct MediaFileTagsEntry {
pub media_file_token: MediaFileToken,
pub tags: Vec<TagDetails>,
}
#[derive(Serialize, ToSchema)]
pub struct BulkListMediaFileTagsSuccessResponse {
pub success: bool,
/// One entry per requested media file token (deduped), in request order.
pub media_files: Vec<MediaFileTagsEntry>,
}
@@ -0,0 +1,44 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── POST /v1/tags/bulk_set ──
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
/// both or neither). Tag text is trimmed and deduped case-insensitively;
/// empty entries are dropped. Unlike `bulk_add`, sanitizing down to zero
/// tags is allowed — it clears all tags from the listed media files.
/// Media file tokens are deduped; tokens the user doesn't own (or that
/// are deleted) are silently skipped.
#[derive(Deserialize, ToSchema)]
pub struct BulkSetTagsRequest {
/// Media files whose tag sets will be replaced.
pub media_file_tokens: Vec<MediaFileToken>,
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
pub maybe_tags: Option<String>,
/// Tags as a list. Entries are still trimmed.
pub maybe_tags_list: Option<Vec<String>>,
}
#[derive(Serialize, ToSchema)]
pub struct BulkSetTagsSuccessResponse {
pub success: bool,
/// The subset of the input tokens that were actually updated: files
/// that exist, aren't deleted, and are owned by the caller.
pub accepted_media_file_tokens: Vec<MediaFileToken>,
/// The full tag set now on every accepted media file, with canonical
/// tokens and fresh use counts.
pub tags: Vec<TagDetails>,
/// How many previously-attached tag links were removed across all the
/// accepted media files because they weren't mentioned in the request.
/// (Orphaned tags are not deleted.)
pub removed_count: u64,
}
@@ -0,0 +1,20 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
// ── POST /v1/tags/media_file/clear/{media_file_token} ──
#[derive(Deserialize, ToSchema)]
pub struct ClearMediaFileTagsPathInfo {
pub media_file_token: MediaFileToken,
}
#[derive(Serialize, ToSchema)]
pub struct ClearMediaFileTagsSuccessResponse {
pub success: bool,
/// How many tag links were removed from the media file. (Orphaned
/// tags are not deleted.)
pub removed_count: u64,
}
@@ -0,0 +1,20 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::tags::TagToken;
/// One tag as returned by every tags endpoint.
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct TagDetails {
pub tag_token: TagToken,
/// The display value of the tag, as entered by its creator.
pub tag_value: String,
/// Lowercased form of `tag_value`. This is the tag's unique key
/// within the creator's account.
pub tag_value_lowercase: String,
/// Rollup statistic: how many media files currently carry this tag.
pub use_count: u32,
}
@@ -0,0 +1,20 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::tags::TagToken;
// ── DELETE /v1/tags/{tag_token} ──
#[derive(Deserialize, ToSchema)]
pub struct DeleteTagPathInfo {
pub tag_token: TagToken,
}
#[derive(Serialize, ToSchema)]
pub struct DeleteTagSuccessResponse {
pub success: bool,
/// How many media-file links were hard-deleted along with the tag.
/// (The tag record itself is soft-deleted.)
pub removed_link_count: u64,
}
@@ -0,0 +1,21 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── GET /v1/tags/media_file/list/{media_file_token} ──
#[derive(Deserialize, ToSchema)]
pub struct ListMediaFileTagsPathInfo {
pub media_file_token: MediaFileToken,
}
#[derive(Serialize, ToSchema)]
pub struct ListMediaFileTagsSuccessResponse {
pub success: bool,
/// All (live) tags on the media file, sorted by tag value.
pub tags: Vec<TagDetails>,
}
@@ -0,0 +1,22 @@
use serde_derive::Deserialize;
use utoipa::{IntoParams, ToSchema};
use tokens::tokens::tags::TagToken;
// ── GET /v1/tags/media_files/with_tag/{tag_token} ──
//
// NB: The success response lives in `storyteller_web`'s handler because
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
// constructors that depend on the request's `MediaDomain` +
// `ServerEnvironment`.
#[derive(Deserialize, ToSchema)]
pub struct ListMediaFilesWithTagPathInfo {
pub tag_token: TagToken,
}
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListMediaFilesWithTagQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
}
@@ -0,0 +1,15 @@
use serde_derive::Deserialize;
use utoipa::{IntoParams, ToSchema};
// ── GET /v1/tags/media_files/list_tagged ──
//
// NB: The success response lives in `storyteller_web`'s handler because
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
// constructors that depend on the request's `MediaDomain` +
// `ServerEnvironment`.
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListTaggedMediaFilesQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
}
@@ -0,0 +1,23 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use crate::tags::common::TagDetails;
// ── GET /v1/tags/list ──
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListTagsQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
}
#[derive(Serialize, ToSchema)]
pub struct ListTagsSuccessResponse {
pub success: bool,
/// The logged-in user's (live) tags, newest first.
pub tags: Vec<TagDetails>,
/// Present when there may be more results; pass back as `cursor`.
pub maybe_cursor: Option<String>,
}
@@ -0,0 +1,15 @@
use serde_derive::Deserialize;
use utoipa::{IntoParams, ToSchema};
// ── GET /v1/tags/media_files/list_untagged ──
//
// NB: The success response lives in `storyteller_web`'s handler because
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
// constructors that depend on the request's `MediaDomain` +
// `ServerEnvironment`.
#[derive(Deserialize, ToSchema, IntoParams)]
pub struct ListUntaggedMediaFilesQueryParams {
pub cursor: Option<String>,
pub limit: Option<u32>,
}
@@ -0,0 +1,14 @@
pub mod add_media_file_tags;
pub mod bulk_add_tags;
pub mod bulk_list_media_file_tags;
pub mod bulk_set_tags;
pub mod clear_media_file_tags;
pub mod common;
pub mod delete_tag;
pub mod list_media_file_tags;
pub mod list_media_files_with_tag;
pub mod list_tagged_media_files;
pub mod list_tags;
pub mod list_untagged_media_files;
pub mod rename_tag;
pub mod set_media_file_tags;
@@ -0,0 +1,29 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::tags::TagToken;
use crate::tags::common::TagDetails;
// ── PUT /v1/tags/rename/{tag_token} ──
#[derive(Deserialize, ToSchema)]
pub struct RenameTagPathInfo {
pub tag_token: TagToken,
}
#[derive(Deserialize, ToSchema)]
pub struct RenameTagRequest {
/// The new tag text (trimmed server-side). Both case-only changes and
/// wholesale renames are allowed. Fails with 400 if the caller already
/// has a different tag with the same lowercased value.
pub new_tag_value: String,
}
#[derive(Serialize, ToSchema)]
pub struct RenameTagSuccessResponse {
pub success: bool,
/// The tag after the rename.
pub tag: TagDetails,
}
@@ -0,0 +1,39 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
use crate::tags::common::TagDetails;
// ── POST /v1/tags/media_file/set/{media_file_token} ──
#[derive(Deserialize, ToSchema)]
pub struct SetMediaFileTagsPathInfo {
pub media_file_token: MediaFileToken,
}
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
/// both or neither). Tag text is trimmed and deduped case-insensitively;
/// empty entries are dropped. Unlike `add`, sanitizing down to zero tags
/// is allowed — it clears all tags from the media file.
#[derive(Deserialize, ToSchema)]
pub struct SetMediaFileTagsRequest {
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
pub maybe_tags: Option<String>,
/// Tags as a list. Entries are still trimmed.
pub maybe_tags_list: Option<Vec<String>>,
}
#[derive(Serialize, ToSchema)]
pub struct SetMediaFileTagsSuccessResponse {
pub success: bool,
/// The media file's full tag set after the operation, with canonical
/// tokens and fresh use counts.
pub tags: Vec<TagDetails>,
/// How many previously-attached tag links were removed because they
/// weren't mentioned in the request. (Orphaned tags are not deleted.)
pub removed_count: u64,
}
@@ -22,7 +22,7 @@ artcraft_client.workspace = true
fal_client.workspace = true
gmicloud_client.workspace = true
grok_api_client.workspace = true
seedance2pro_client.workspace = true
kinovi_web_client.workspace = true
tokens.workspace = true
url_utils.workspace = true
worldlabs_api_client.workspace = true
@@ -41,6 +41,10 @@ pub enum RouterImageModel {
Seedream4p5,
#[serde(rename = "seedream_5_lite")]
Seedream5Lite,
#[serde(rename = "seedream_5p0_pro")]
Seedream5p0Pro,
#[serde(rename = "seedream_5p0_pro_u")]
Seedream5p0ProUltra,
#[serde(rename = "qwen_edit_2511_angles")]
QwenEdit2511Angles,
#[serde(rename = "flux_2_lora_angles")]
@@ -73,4 +77,14 @@ mod tests {
fn midjourney_8_serializes_to_midjourney_8() {
assert_round_trip(RouterImageModel::Midjourney8, "midjourney_8");
}
#[test]
fn seedream_5p0_pro_serializes_to_seedream_5p0_pro() {
assert_round_trip(RouterImageModel::Seedream5p0Pro, "seedream_5p0_pro");
}
#[test]
fn seedream_5p0_pro_ultra_serializes_to_seedream_5p0_pro_u() {
assert_round_trip(RouterImageModel::Seedream5p0ProUltra, "seedream_5p0_pro_u");
}
}
@@ -8,6 +8,6 @@ pub enum RouterProvider {
Fal,
GmiCloud,
GrokApi,
Seedance2Pro,
KinoviWeb,
WorldLabs,
}
@@ -14,6 +14,12 @@ pub enum RouterVideoModel {
#[serde(rename = "grok_imagine_video_1p5")]
GrokImagineVideo1p5,
#[serde(rename = "flux_3")]
Flux3,
#[serde(rename = "flux_3_draft")]
Flux3Draft,
#[serde(rename = "kling_1p6_pro")]
Kling16Pro,
@@ -35,6 +41,15 @@ pub enum RouterVideoModel {
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "minimax_h3")]
MinimaxH3,
#[serde(rename = "minimax_h3_turbo")]
MinimaxH3Turbo,
#[serde(rename = "minimax_h3_ultra")]
MinimaxH3Ultra,
#[serde(rename = "seedance_1p0_lite")]
Seedance10Lite,
@@ -74,6 +89,12 @@ pub enum RouterVideoModel {
#[serde(rename = "seedance_2p0_bpu_mini")]
Seedance2p0BytePlusUltraMini,
#[serde(rename = "seedance_2p5_preview")]
Seedance2p5Preview,
#[serde(rename = "seedance_2p5")]
Seedance2p5,
#[serde(rename = "happy_horse_1p0")]
HappyHorse1p0,
@@ -133,6 +154,18 @@ mod tests {
assert_serde_round_trip(RouterVideoModel::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini");
}
#[test]
fn seedance_2p5_preview_serializes() {
assert_serde_round_trip(RouterVideoModel::Seedance2p5Preview, "seedance_2p5_preview");
}
#[test]
fn seedance_2p5_serializes() {
// NB: This string must match `CommonVideoModel` — the two enums convert
// via serde string round-trip in the omni handlers.
assert_serde_round_trip(RouterVideoModel::Seedance2p5, "seedance_2p5");
}
#[test]
fn veo_3p1_lite_and_vidu_variants_serialize() {
// NB: These strings must match `CommonVideoModel` — the two enums convert
@@ -141,4 +174,27 @@ mod tests {
assert_serde_round_trip(RouterVideoModel::ViduQ3, "vidu_q3");
assert_serde_round_trip(RouterVideoModel::ViduQ3Turbo, "vidu_q3_turbo");
}
#[test]
fn flux_3_variants_serialize() {
// NB: These strings must match `CommonVideoModel` — the two enums convert
// via serde string round-trip in the omni handlers.
assert_serde_round_trip(RouterVideoModel::Flux3, "flux_3");
assert_serde_round_trip(RouterVideoModel::Flux3Draft, "flux_3_draft");
}
#[test]
fn minimax_h3_serializes() {
// NB: This string must match `CommonVideoModel` — the two enums convert
// via serde string round-trip in the omni handlers.
assert_serde_round_trip(RouterVideoModel::MinimaxH3, "minimax_h3");
}
#[test]
fn minimax_h3_turbo_and_ultra_serialize() {
// NB: These strings must match `CommonVideoModel` — the two enums convert
// via serde string round-trip in the omni handlers.
assert_serde_round_trip(RouterVideoModel::MinimaxH3Turbo, "minimax_h3_turbo");
assert_serde_round_trip(RouterVideoModel::MinimaxH3Ultra, "minimax_h3_ultra");
}
}
@@ -7,5 +7,5 @@ pub mod router_client;
pub mod router_fal_client;
pub mod router_gmicloud_client;
pub mod router_grok_api_client;
pub mod router_seedance2pro_client;
pub mod router_kinovi_web_client;
pub mod router_worldlabs_client;
@@ -2,7 +2,7 @@ use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::client::router_kinovi_web_client::RouterKinoviWebClient;
use crate::client::router_worldlabs_client::RouterWorldLabsClient;
use crate::errors::client_error::{ClientError, ClientType};
@@ -11,7 +11,7 @@ pub struct MultiRouterClient {
pub(crate) fal_client: Option<RouterFalClient>,
pub(crate) gmicloud_client: Option<RouterGmiCloudClient>,
pub(crate) grok_api_client: Option<RouterGrokApiClient>,
pub(crate) seedance2pro_client: Option<RouterSeedance2ProClient>,
pub(crate) kinovi_web_client: Option<RouterKinoviWebClient>,
pub(crate) worldlabs_client: Option<RouterWorldLabsClient>,
}
@@ -36,9 +36,9 @@ impl MultiRouterClient {
.ok_or(ClientError::ClientNotConfigured(ClientType::GrokApi))
}
pub fn get_seedance2pro_client_ref(&self) -> Result<&RouterSeedance2ProClient, ClientError> {
self.seedance2pro_client.as_ref()
.ok_or(ClientError::ClientNotConfigured(ClientType::Seedance2Pro))
pub fn get_kinovi_web_client_ref(&self) -> Result<&RouterKinoviWebClient, ClientError> {
self.kinovi_web_client.as_ref()
.ok_or(ClientError::ClientNotConfigured(ClientType::KinoviWeb))
}
pub fn get_worldlabs_client_ref(&self) -> Result<&RouterWorldLabsClient, ClientError> {
@@ -3,7 +3,7 @@ use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::client::router_kinovi_web_client::RouterKinoviWebClient;
use crate::client::router_worldlabs_client::RouterWorldLabsClient;
pub struct MultiRouterClientBuilder {
@@ -11,7 +11,7 @@ pub struct MultiRouterClientBuilder {
fal_client: Option<RouterFalClient>,
gmicloud_client: Option<RouterGmiCloudClient>,
grok_api_client: Option<RouterGrokApiClient>,
seedance2pro_client: Option<RouterSeedance2ProClient>,
kinovi_web_client: Option<RouterKinoviWebClient>,
worldlabs_client: Option<RouterWorldLabsClient>,
}
@@ -22,7 +22,7 @@ impl MultiRouterClientBuilder {
fal_client: None,
gmicloud_client: None,
grok_api_client: None,
seedance2pro_client: None,
kinovi_web_client: None,
worldlabs_client: None,
}
}
@@ -47,8 +47,8 @@ impl MultiRouterClientBuilder {
self
}
pub fn set_seedance2pro_client(mut self, client: RouterSeedance2ProClient) -> Self {
self.seedance2pro_client = Some(client);
pub fn set_kinovi_web_client(mut self, client: RouterKinoviWebClient) -> Self {
self.kinovi_web_client = Some(client);
self
}
@@ -63,7 +63,7 @@ impl MultiRouterClientBuilder {
fal_client: self.fal_client,
gmicloud_client: self.gmicloud_client,
grok_api_client: self.grok_api_client,
seedance2pro_client: self.seedance2pro_client,
kinovi_web_client: self.kinovi_web_client,
worldlabs_client: self.worldlabs_client,
}
}
@@ -3,7 +3,7 @@ use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::client::router_kinovi_web_client::RouterKinoviWebClient;
use crate::client::router_worldlabs_client::RouterWorldLabsClient;
use crate::errors::client_error::{ClientError, ClientType};
@@ -13,7 +13,7 @@ pub enum RouterClient {
Fal(RouterFalClient),
GmiCloud(RouterGmiCloudClient),
GrokApi(RouterGrokApiClient),
Seedance2Pro(RouterSeedance2ProClient),
KinoviWeb(RouterKinoviWebClient),
WorldLabs(RouterWorldLabsClient),
}
@@ -50,11 +50,11 @@ impl RouterClient {
}
}
pub fn get_seedance2pro_client_ref(&self) -> Result<&RouterSeedance2ProClient, ClientError> {
pub fn get_kinovi_web_client_ref(&self) -> Result<&RouterKinoviWebClient, ClientError> {
match self {
RouterClient::Seedance2Pro(client) => Ok(client),
RouterClient::Multi(multi) => multi.get_seedance2pro_client_ref(),
_ => Err(ClientError::ClientNotConfigured(ClientType::Seedance2Pro)),
RouterClient::KinoviWeb(client) => Ok(client),
RouterClient::Multi(multi) => multi.get_kinovi_web_client_ref(),
_ => Err(ClientError::ClientNotConfigured(ClientType::KinoviWeb)),
}
}
@@ -0,0 +1,11 @@
use kinovi_web_client::creds::kinovi_web_session::KinoviWebSession;
pub struct RouterKinoviWebClient {
pub(crate) session: KinoviWebSession,
}
impl RouterKinoviWebClient {
pub fn new(session: KinoviWebSession) -> Self {
RouterKinoviWebClient { session }
}
}
@@ -1,11 +0,0 @@
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
pub struct RouterSeedance2ProClient {
pub(crate) session: Seedance2ProSession,
}
impl RouterSeedance2ProClient {
pub fn new(session: Seedance2ProSession) -> Self {
RouterSeedance2ProClient { session }
}
}
@@ -2,8 +2,8 @@ use crate::errors::client_error::ClientError;
use crate::errors::download_error::DownloadError;
use crate::errors::provider_error::ProviderError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use seedance2pro_client::error::seedance2pro_error::Seedance2ProError;
use seedance2pro_client::error::seedance2pro_specific_api_error::Seedance2ProSpecificApiError;
use kinovi_web_client::error::kinovi_web_error::KinoviWebError;
use kinovi_web_client::error::kinovi_web_specific_api_error::KinoviWebSpecificApiError;
use std::error::Error;
use std::fmt::{Display, Formatter};
use artcraft_client::error::api_error::ApiError;
@@ -65,7 +65,7 @@ impl From<ProviderError> for ArtcraftRouterError {
fn from(error: ProviderError) -> Self {
let is_billing_error = match &error {
ProviderError::Fal(FalErrorPlus::FalBillingError(_)) => true,
ProviderError::Seedance2Pro(Seedance2ProError::ApiSpecific(Seedance2ProSpecificApiError::BillingError { .. })) => true,
ProviderError::KinoviWeb(KinoviWebError::ApiSpecific(KinoviWebSpecificApiError::BillingError { .. })) => true,
ProviderError::Storyteller(StorytellerError::Api(ApiError::PaymentRequired(_))) => true,
_ => false,
};
@@ -9,7 +9,7 @@ pub enum ClientType {
Fal,
GmiCloud,
GrokApi,
Seedance2Pro,
KinoviWeb,
WorldLabs,
}
@@ -20,7 +20,7 @@ impl Display for ClientType {
Self::Fal => write!(f, "Fal"),
Self::GmiCloud => write!(f, "GmiCloud"),
Self::GrokApi => write!(f, "GrokApi"),
Self::Seedance2Pro => write!(f, "Seedance2Pro"),
Self::KinoviWeb => write!(f, "KinoviWeb"),
Self::WorldLabs => write!(f, "WorldLabs"),
}
}
@@ -47,8 +47,8 @@ pub enum ClientError {
/// Fal only accepts image URLs for image inputs, not media tokens.
FalOnlySupportsUrls,
/// Seedance2Pro only accepts URLs for media inputs, not media tokens.
Seedance2ProOnlySupportsUrls,
/// KinoviWeb only accepts URLs for media inputs, not media tokens.
KinoviWebOnlySupportsUrls,
/// The Fal endpoint requires a webhook URL but the caller built the client
/// in polling/queue mode (no webhook URL). Returned by webhook-only
@@ -91,8 +91,8 @@ impl Display for ClientError {
Self::FalOnlySupportsUrls => {
write!(f, "Fal only supports image URLs for image inputs, not media tokens")
}
Self::Seedance2ProOnlySupportsUrls => {
write!(f, "Seedance2Pro only supports URLs for media inputs; resolve media tokens to URLs before calling this provider")
Self::KinoviWebOnlySupportsUrls => {
write!(f, "KinoviWeb only supports URLs for media inputs; resolve media tokens to URLs before calling this provider")
}
Self::WebhookUrlRequired => {
write!(f, "This Fal endpoint only supports webhook dispatch; the caller built RouterFalClient in polling-only mode (no webhook URL)")
@@ -4,7 +4,7 @@ use artcraft_client::error::storyteller_error::StorytellerError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use gmicloud_client::error::gmicloud_error::GmiCloudError;
use grok_api_client::error::grok_error::GrokError;
use seedance2pro_client::error::seedance2pro_error::Seedance2ProError;
use kinovi_web_client::error::kinovi_web_error::KinoviWebError;
use worldlabs_api_client::error::world_labs_error::WorldLabsError;
#[derive(Debug)]
@@ -13,7 +13,7 @@ pub enum ProviderError {
Fal(FalErrorPlus),
GmiCloud(GmiCloudError),
Grok(GrokError),
Seedance2Pro(Seedance2ProError),
KinoviWeb(KinoviWebError),
WorldLabs(WorldLabsError),
}
@@ -26,7 +26,7 @@ impl Display for ProviderError {
Self::Fal(e) => write!(f, "Fal provider error: {}", e),
Self::GmiCloud(e) => write!(f, "GmiCloud provider error: {}", e),
Self::Grok(e) => write!(f, "Grok provider error: {}", e),
Self::Seedance2Pro(e) => write!(f, "Seedance2Pro provider error: {}", e),
Self::KinoviWeb(e) => write!(f, "KinoviWeb provider error: {}", e),
Self::WorldLabs(e) => write!(f, "WorldLabs provider error: {}", e),
}
}
@@ -56,9 +56,9 @@ impl From<GrokError> for ProviderError {
}
}
impl From<Seedance2ProError> for ProviderError {
fn from(error: Seedance2ProError) -> Self {
Self::Seedance2Pro(error)
impl From<KinoviWebError> for ProviderError {
fn from(error: KinoviWebError) -> Self {
Self::KinoviWeb(error)
}
}
@@ -26,8 +26,8 @@ impl AudioGenerationDraftRequest {
pub fn get_provider(&self) -> RouterProvider {
match self {
Self::KinoviSunoRemix(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoSample(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoRemix(_) => RouterProvider::KinoviWeb,
Self::KinoviSunoSample(_) => RouterProvider::KinoviWeb,
}
}
@@ -4,7 +4,7 @@ use std::fmt::Debug;
use tokens::tokens::media_files::MediaFileToken;
use crate::client::router_client::RouterClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::client::router_kinovi_web_client::RouterKinoviWebClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
@@ -19,9 +19,9 @@ pub struct AudioGenerationDraftContext<'a> {
}
impl <'a> AudioGenerationDraftContext<'a> {
pub fn get_seedance2pro_client_ref(&self) -> Result<&RouterSeedance2ProClient, ArtcraftRouterError> {
pub fn get_kinovi_web_client_ref(&self) -> Result<&RouterKinoviWebClient, ArtcraftRouterError> {
let client = self.client.ok_or(ArtcraftRouterError::Client(ClientError::RouterClientNotProvided))?;
client.get_seedance2pro_client_ref()
client.get_kinovi_web_client_ref()
.map_err(|err| ArtcraftRouterError::Client(err))
}
}
@@ -48,10 +48,10 @@ impl AudioGenerationRequest {
Self::ArtcraftSunoSample(_) => RouterProvider::Artcraft,
Self::ArtcraftSeedAudio1p0(_) => RouterProvider::Artcraft,
Self::FalSeedAudio1p0(_) => RouterProvider::Fal,
Self::KinoviSunoMusic(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoRemix(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoSounds(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoSample(_) => RouterProvider::Seedance2Pro,
Self::KinoviSunoMusic(_) => RouterProvider::KinoviWeb,
Self::KinoviSunoRemix(_) => RouterProvider::KinoviWeb,
Self::KinoviSunoSounds(_) => RouterProvider::KinoviWeb,
Self::KinoviSunoSample(_) => RouterProvider::KinoviWeb,
}
}
@@ -100,19 +100,19 @@ impl AudioGenerationRequest {
request.send(client_ref).await
},
AudioGenerationRequest::KinoviSunoMusic(request) => {
let client_ref = client.get_seedance2pro_client_ref()?;
let client_ref = client.get_kinovi_web_client_ref()?;
request.send(client_ref).await
},
AudioGenerationRequest::KinoviSunoRemix(request) => {
let client_ref = client.get_seedance2pro_client_ref()?;
let client_ref = client.get_kinovi_web_client_ref()?;
request.send(client_ref).await
},
AudioGenerationRequest::KinoviSunoSounds(request) => {
let client_ref = client.get_seedance2pro_client_ref()?;
let client_ref = client.get_kinovi_web_client_ref()?;
request.send(client_ref).await
},
AudioGenerationRequest::KinoviSunoSample(request) => {
let client_ref = client.get_seedance2pro_client_ref()?;
let client_ref = client.get_kinovi_web_client_ref()?;
request.send(client_ref).await
},
}
@@ -116,10 +116,10 @@ impl GenerateAudioRequestBuilder {
// Fal
(RouterProvider::Fal, RouterAudioModel::SeedAudio1p0) => build_fal_seed_audio_1p0(self),
// Kinovi
(RouterProvider::Seedance2Pro, RouterAudioModel::SunoMusic) => build_kinovi_suno_music(self),
(RouterProvider::Seedance2Pro, RouterAudioModel::SunoRemix) => build_kinovi_suno_remix(self),
(RouterProvider::Seedance2Pro, RouterAudioModel::SunoSounds) => build_kinovi_suno_sounds(self),
(RouterProvider::Seedance2Pro, RouterAudioModel::SunoSample) => build_kinovi_suno_sample(self),
(RouterProvider::KinoviWeb, RouterAudioModel::SunoMusic) => build_kinovi_suno_music(self),
(RouterProvider::KinoviWeb, RouterAudioModel::SunoRemix) => build_kinovi_suno_remix(self),
(RouterProvider::KinoviWeb, RouterAudioModel::SunoSounds) => build_kinovi_suno_sounds(self),
(RouterProvider::KinoviWeb, RouterAudioModel::SunoSample) => build_kinovi_suno_sample(self),
_ => self.unsupported_provider_and_model(),
}
}
@@ -202,7 +202,7 @@ mod tests {
#[test]
fn kinovi_suno_music_dispatches_to_request() {
let result = builder(RouterProvider::Seedance2Pro, RouterAudioModel::SunoMusic).build2().expect("build");
let result = builder(RouterProvider::KinoviWeb, RouterAudioModel::SunoMusic).build2().expect("build");
assert!(matches!(
result,
AudioGenerationDraftOrRequest::Request(AudioGenerationRequest::KinoviSunoMusic(_))
@@ -211,7 +211,7 @@ mod tests {
#[test]
fn kinovi_suno_sounds_dispatches_to_request() {
let result = builder(RouterProvider::Seedance2Pro, RouterAudioModel::SunoSounds).build2().expect("build");
let result = builder(RouterProvider::KinoviWeb, RouterAudioModel::SunoSounds).build2().expect("build");
assert!(matches!(
result,
AudioGenerationDraftOrRequest::Request(AudioGenerationRequest::KinoviSunoSounds(_))
@@ -220,7 +220,7 @@ mod tests {
#[test]
fn kinovi_suno_remix_dispatches_to_draft() {
let result = builder_with_audio_ref(RouterProvider::Seedance2Pro, RouterAudioModel::SunoRemix).build2().expect("build");
let result = builder_with_audio_ref(RouterProvider::KinoviWeb, RouterAudioModel::SunoRemix).build2().expect("build");
assert!(matches!(
result,
AudioGenerationDraftOrRequest::Draft(AudioGenerationDraftRequest::KinoviSunoRemix(_))
@@ -229,7 +229,7 @@ mod tests {
#[test]
fn kinovi_suno_sample_dispatches_to_draft() {
let result = builder_with_audio_ref(RouterProvider::Seedance2Pro, RouterAudioModel::SunoSample).build2().expect("build");
let result = builder_with_audio_ref(RouterProvider::KinoviWeb, RouterAudioModel::SunoSample).build2().expect("build");
assert!(matches!(
result,
AudioGenerationDraftOrRequest::Draft(AudioGenerationDraftRequest::KinoviSunoSample(_))
@@ -242,7 +242,7 @@ mod tests {
#[test]
fn kinovi_seed_audio_1p0_is_unsupported() {
let result = builder(RouterProvider::Seedance2Pro, RouterAudioModel::SeedAudio1p0).build2();
let result = builder(RouterProvider::KinoviWeb, RouterAudioModel::SeedAudio1p0).build2();
assert!(matches!(result, Err(ArtcraftRouterError::UnsupportedProviderAndModelForNewApi(_))));
}
@@ -10,7 +10,7 @@ pub struct ArtcraftAudioResponsePayload {
}
#[derive(Clone, Debug)]
pub struct Seedance2proAudioResponsePayload {
pub struct KinoviWebAudioResponsePayload {
pub order_id: String,
pub task_id: String,
}
@@ -37,7 +37,7 @@ pub struct FalAudioResponsePayload {
#[derive(Clone, Debug)]
pub enum GenerateAudioResponse {
Artcraft(ArtcraftAudioResponsePayload),
Seedance2Pro(Seedance2proAudioResponsePayload),
KinoviWeb(KinoviWebAudioResponsePayload),
Fal(FalAudioResponsePayload),
}
@@ -49,9 +49,9 @@ impl GenerateAudioResponse {
}
}
pub fn get_seedance2pro_payload(&self) -> Option<Seedance2proAudioResponsePayload> {
pub fn get_kinovi_web_payload(&self) -> Option<KinoviWebAudioResponsePayload> {
match self {
Self::Seedance2Pro(p) => Some(p.clone()),
Self::KinoviWeb(p) => Some(p.clone()),
_ => None,
}
}
@@ -5,14 +5,14 @@
//! draft `finalize()` phase. The upload plumbing is shared with the Kinovi
//! video provider (`generate_video::providers::kinovi::upload`).
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
use kinovi_web_client::creds::kinovi_web_session::KinoviWebSession;
use tokens::tokens::media_files::MediaFileToken;
use crate::api::audio_list_ref::AudioListRef;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_audio::audio_generation_draft_context::AudioGenerationDraftContext;
use crate::generate::generate_video::providers::kinovi::upload::upload_to_seedance2pro;
use crate::generate::generate_video::providers::kinovi::upload::upload_to_kinovi_web;
/// A single unresolved audio reference: either an ArtCraft/public URL or a
/// media file token that resolves to one via the draft context's map.
@@ -55,7 +55,7 @@ pub(crate) fn require_single_audio_ref(
/// Resolve a single audio reference to a source URL and re-upload it to the
/// Kinovi CDN, returning the Kinovi CDN URL.
pub(crate) async fn resolve_and_upload_audio_ref(
session: &Seedance2ProSession,
session: &KinoviWebSession,
audio_ref: &SingleAudioRef,
draft_context: &AudioGenerationDraftContext<'_>,
) -> Result<String, ArtcraftRouterError> {
@@ -71,7 +71,7 @@ pub(crate) async fn resolve_and_upload_audio_ref(
})?
}
};
upload_to_seedance2pro(session, &source_url).await
upload_to_kinovi_web(session, &source_url, None).await
}
#[cfg(test)]
@@ -1,4 +1,4 @@
use seedance2pro_client::generate::audio::generate_suno_music::GenerateSunoMusicRequest;
use kinovi_web_client::generate::audio::generate_suno_music::GenerateSunoMusicRequest;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::generate::generate_audio::audio_generation_draft_or_request::AudioGenerationDraftOrRequest;
@@ -119,7 +119,7 @@ mod tests {
fn base_builder() -> GenerateAudioRequestBuilder {
GenerateAudioRequestBuilder {
model: RouterAudioModel::SunoMusic,
provider: RouterProvider::Seedance2Pro,
provider: RouterProvider::KinoviWeb,
prompt: Some("a song about corgis".to_string()),
style_prompt: Some("sea shanty".to_string()),
..Default::default()
@@ -1,4 +1,4 @@
use seedance2pro_client::generate::audio::generate_suno_music::GenerateSunoMusicRequest;
use kinovi_web_client::generate::audio::generate_suno_music::GenerateSunoMusicRequest;
use crate::generate::generate_audio::audio_generation_cost_estimate::AudioGenerationCostEstimate;
use crate::generate::generate_audio::providers::kinovi::suno_music::request::KinoviSunoMusicRequestState;
@@ -13,7 +13,7 @@ impl KinoviSunoMusicCostState {
}
pub fn estimate_cost(&self) -> AudioGenerationCostEstimate {
// Cost math is owned by seedance2pro_client's binding — the router just
// Cost math is owned by kinovi_web_client's binding — the router just
// forwards the result so router cost ≡ binding cost by construction.
let pricing_request = GenerateSunoMusicRequest {
prompt: String::new(),
@@ -54,7 +54,7 @@ mod tests {
fn cost_from_built_request_is_seven_cents() {
let builder = GenerateAudioRequestBuilder {
model: RouterAudioModel::SunoMusic,
provider: RouterProvider::Seedance2Pro,
provider: RouterProvider::KinoviWeb,
prompt: Some("a song".to_string()),
..Default::default()
};

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