mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-13 14:59:05 +00:00
## What
A new workspace member, `artcraft-native/`. It is the ArtCraft desktop
app in pure Rust: an egui UI with no Tauri, no webview and no
JavaScript. It covers the two main product pages, **Create Image** and
**Create Video**, and reuses the Tauri-free API crates
(`artcraft_client`, `artcraft_api_defs`, `tokens`, `enums`).
It also adds:
- a Home page and a Library page
- a lightbox
- sign-in and settings dialogs
- a sidebar and top bar in the style of the brutalist
**artcraft-webapp**
Tools that aren't ported yet show a "coming soon" page that links to the
web app.
```bash
cargo run -p artcraft_native --release
```
## Why
To get a native, webview-free desktop app. Generation goes through
**OmniGen** (`/v1/omni_gen/{models,cost,generate}`), the same API as the
webapp. Model capabilities come from the server, so the pickers stay
current without app updates.
## Prompt box
Recreated feature for feature from `PromptBoxImage` / `PromptBoxVideo`:
- **Editor:** auto-grow, focus mode, resize grip, length counter with
the model's limit, Enter-to-generate setting, and `@Image1` / `@Video1`
/ `@Audio1` mentions with highlighting and autocomplete.
- **References:** the fanned reference deck (hover-expand, reorder, add
menus with limits) and the first/last keyframe slots with swap. Files
can be uploaded, picked from the library, dragged and dropped, or pasted
as images.
- **Model selector:** families with a flyout, maker logos and capability
badges.
- **Pickers:** aspect ratio, resolution, quality, count, bitrate, output
format, duration slider, sound and input mode (Keyframe / Omni
Reference).
- **Generate:** a live credit cost estimate, validation (starting frame,
text-only models, limits, uploads in progress), and clear-all with
confirmation.
The feed shows running jobs (with progress and time left), failures
(dismissable), finished results and the paginated library, in a masonry
grid or a list. It supports Recreate, Make Video, Share and Download,
plus select mode for batch downloads.
## Reviewer notes
- **Sign-in.** On first launch the app reuses an existing desktop login
by reading the session cookie from the Tauri app's jar, and keeps its
own copy in `~/Artcraft/credentials/native_session.json`. Browser
(device-code) and password sign-in are also available.
- **Decoding.**
- Generate and cost requests are sent as JSON maps, like the Tauri app's
omni path.
- Jobs, library, batch and prompt responses use lenient structs
(`backend/wire.rs`), so new server enum values can't break decoding.
- **Renderer.** It uses eframe's **glow** renderer. `wgpu-hal` fails to
build against the `windows` crate versions this workspace pins for
Tauri.
- **Model names and logos.** Known families use curated names: "Flux 1
Dev" rather than the server's "FLUX.1 [dev]". When the server reports
`artcraft` as a model's maker, the logo comes from the id prefix instead
(Nano Banana → Google), matching the webapp.
- **Fonts and assets.** Inter, Archivo and Geist Mono are bundled under
the OFL and listed in `NOTICE`. The wordmark, icon and maker logos are
the existing in-repo assets.
- **Diagnostics.** `ARTCRAFT_SCREENSHOT=<png>` (optionally with
`ARTCRAFT_PAGE` and `ARTCRAFT_SCREENSHOT_EXIT=1`) saves a screenshot
once the window has settled.
## Testing
- `cargo test -p artcraft_native`: 29 tests pass. Unit tests cover the
catalog, feed store, wire decoding and backend helpers. Headless
`egui_kittest` tests drive the prompt box: generate gating,
Enter-to-generate, pickers, `@` mentions and clear-all.
- `cargo clippy -p artcraft_native --all-targets --no-deps` is clean.
- Run against a real account: the session import, model lists, cost
estimates, job polling, library paging and thumbnails all worked.
- **Not exercised: a real paid generation and real uploads.** I held
back to avoid spending credits, so they need a manual check.
## Not done yet
- `@Character` mentions for Seedance 2.0
- Provider choice (Midjourney direct, fal); everything runs through
ArtCraft
- In-app video and audio playback; these open in the system player
- Animated previews in the feed
- The remaining tools (Audio, 3D, editors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.3 KiB
TOML
130 lines
5.3 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
|
|
# Desktop application and its local build/test dependencies.
|
|
members = [
|
|
"artcraft-native", # Pure Rust (egui) desktop app; no Tauri or webview
|
|
"crates/api_clients/artcraft/artcraft_api_defs",
|
|
"crates/api_clients/artcraft/artcraft_client",
|
|
"crates/api_clients/artcraft/artcraft_router",
|
|
"crates/api_clients/fal_client",
|
|
"crates/api_clients/gmicloud_client",
|
|
"crates/api_clients/grok_api_client",
|
|
"crates/api_clients/grok_consumer_client",
|
|
"crates/api_clients/kinovi_web_client",
|
|
"crates/api_clients/midjourney_client",
|
|
"crates/api_clients/openai_sora_client",
|
|
"crates/api_clients/worldlabs_api_client",
|
|
"crates/api_clients/worldlabs_consumer_client",
|
|
"crates/desktop/artcraft",
|
|
"crates/lib/artcraft_client_identity",
|
|
"crates/lib/browser_emulation",
|
|
"crates/lib/build_metadata",
|
|
"crates/lib/caching/memory_store",
|
|
"crates/lib/cloudflare_errors",
|
|
"crates/lib/cloudflare_mitigation",
|
|
"crates/lib/cookie_store",
|
|
"crates/lib/cookie_store_wrapper",
|
|
"crates/lib/data/primitives",
|
|
"crates/lib/data/web_base64",
|
|
"crates/lib/errors",
|
|
"crates/lib/files/file_to_base64_url",
|
|
"crates/lib/files/filesys",
|
|
"crates/lib/files/images",
|
|
"crates/lib/files/mimetypes",
|
|
"crates/lib/http_headers",
|
|
"crates/lib/jwt_light",
|
|
"crates/lib/url_utils",
|
|
"crates/lib/uuid_utils",
|
|
"crates/schema/database/sqlite_tasks",
|
|
"crates/schema/public/enums",
|
|
"crates/schema/public/tokens",
|
|
"crates/testing/test_data",
|
|
"crates/testing/test_utils",
|
|
"crates/vendor/tauri-plugin-http",
|
|
]
|
|
default-members = ["crates/desktop/artcraft"]
|
|
|
|
[workspace.package]
|
|
publish = false
|
|
|
|
[workspace.dependencies]
|
|
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
|
|
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
|
|
artcraft_client_identity = { path = "crates/lib/artcraft_client_identity" }
|
|
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
|
|
browser_emulation = { path = "crates/lib/browser_emulation" }
|
|
cookie_store_wrapper = { path = "crates/lib/cookie_store_wrapper" }
|
|
build_metadata = { path = "crates/lib/build_metadata" }
|
|
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
|
|
cloudflare_mitigation = { path = "crates/lib/cloudflare_mitigation" }
|
|
cookie_store = { path = "crates/lib/cookie_store" }
|
|
enums = { path = "crates/schema/public/enums" }
|
|
errors = { path = "crates/lib/errors" }
|
|
fal_client = { path = "crates/api_clients/fal_client" }
|
|
gmicloud_client = { path = "crates/api_clients/gmicloud_client" }
|
|
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
|
filesys = { path = "crates/lib/files/filesys" }
|
|
grok_api_client = { path = "crates/api_clients/grok_api_client" }
|
|
grok_consumer_client = { path = "crates/api_clients/grok_consumer_client" }
|
|
http_headers = { path = "crates/lib/http_headers" }
|
|
images = { path = "crates/lib/files/images" }
|
|
jwt_light = { path = "crates/lib/jwt_light" }
|
|
memory_store = { path = "crates/lib/caching/memory_store" }
|
|
midjourney_client = { path = "crates/api_clients/midjourney_client" }
|
|
mimetypes = { path = "crates/lib/files/mimetypes" }
|
|
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
|
primitives = { path = "crates/lib/data/primitives" }
|
|
kinovi_web_client = { path = "crates/api_clients/kinovi_web_client" }
|
|
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
|
|
test_data = { path = "crates/testing/test_data" }
|
|
test_utils = { path = "crates/testing/test_utils" }
|
|
tokens = { path = "crates/schema/public/tokens" }
|
|
url_utils = { path = "crates/lib/url_utils" }
|
|
uuid_utils = { path = "crates/lib/uuid_utils" }
|
|
web_base64 = { path = "crates/lib/data/web_base64" }
|
|
worldlabs_api_client = { path = "crates/api_clients/worldlabs_api_client" }
|
|
worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_client" }
|
|
anyhow = "1.0.102"
|
|
assertor = "0.0.3"
|
|
base64 = "0.22.1"
|
|
bstr = "1.12.1"
|
|
byteorder = "1.5.0"
|
|
chrono = { version = "0.4.44", features = ["serde"] }
|
|
cookie = "0.18.1"
|
|
env = "1.0.1"
|
|
env_logger = "0.11.9"
|
|
futures = { version = "0.3.32" }
|
|
indexmap = "2.13.0"
|
|
log = "0.4.29"
|
|
once_cell = "1.21.4"
|
|
rand = "0.9.1"
|
|
regex = "1.12.3"
|
|
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls", "gzip" ] }
|
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
serde_derive = "1.0.228"
|
|
serde_json = "1.0.149"
|
|
sha2 = "0.11.0-rc.5"
|
|
sha3 = "0.10.8"
|
|
strum = { version = "0.28.0", features = ["derive"] }
|
|
tauri = { version = "2.11.2", features = ["unstable", "devtools", "protocol-asset"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
|
|
tauri-plugin-dialog = "2.7.1"
|
|
tauri-plugin-fs = "2.5.1"
|
|
tauri-plugin-log = "2.8.0"
|
|
tauri-plugin-upload = "2.4.0"
|
|
tempdir = "0.3.7"
|
|
tempfile = "3.27.0"
|
|
thiserror = "2.0.18"
|
|
tokio = { version = "1.50.0", features = ["macros"] }
|
|
tokio-util = "0.7.12"
|
|
toml = "1.0.7+spec-1.1.0"
|
|
url = { version = "2.5.8", features = ["serde"] }
|
|
utoipa = { version = "5.4.0", features = ["chrono", "actix_extras", "url"] }
|
|
uuid = "1.22.0"
|
|
walkdir = "2.4.0"
|
|
# wreq native prerequisites:
|
|
# macOS: brew install cmake pkg-config llvm
|
|
# Windows: choco install cmake strawberryperl pkgconfiglite llvm nasm
|
|
wreq-util = "3.0.0-rc.10"
|
|
wreq = { version = "6.0.0-rc.28", features = ["json", "query", "form", "multipart", "cookies", "ws", "stream", "gzip", "brotli", "zstd", "deflate"] }
|