mirror of
https://github.com/aphrody-labs/bun-distribution.git
synced 2026-10-17 12:16:50 +00:00
style: native bun fmt over imported packages, apps and scripts
This commit is contained in:
1 parent
21d7943af8
commit
01b5395ba8
3426 files changed
+519608
-122443
No files matched your search
@@ -44,7 +44,10 @@ perf = { level = "warn", priority = -1 }
|
||||
# it never re-executes its own exe as `__llama-daemon`. Without the feature (no libclang at build
|
||||
# time) the supervisor falls back to the detached daemon, which `src/llama_daemon.rs` still serves.
|
||||
default = ["in-process"]
|
||||
in-process = ["aphrody-app-server/in-process", "aphrody-llama-server/in-process"]
|
||||
in-process = [
|
||||
"aphrody-app-server/in-process",
|
||||
"aphrody-llama-server/in-process",
|
||||
]
|
||||
# Serve the bundled `frontendDist` (ui-dist) instead of `devUrl`. The Tauri CLI enables it for
|
||||
# `tauri build`; plain `cargo build`/`cargo run` release builds must pass it explicitly.
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
@@ -74,10 +77,24 @@ aphrody-subsystem = { path = "../../crates/aphrody-subsystem", version = "1.4.4"
|
||||
async-trait = "0.1.92"
|
||||
yolo-core = "=1.0.0"
|
||||
base64 = "0.23.1"
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
||||
rustls = { version = "0.23", default-features = false, features = [
|
||||
"ring",
|
||||
"std",
|
||||
"tls12",
|
||||
] }
|
||||
tauri = { package = "aphrody-tauri", path = "../../toolchains/bun/packages/bun-tauri/crates/aphrody-tauri", version = "1.4.4", default-features = false, features = ["compression", "dynamic-acl", "image-ico", "tray-icon"] }
|
||||
tauri-runtime-cef = { package = "aphrody-tauri-runtime-cef", path = "../../toolchains/bun/packages/bun-tauri/crates/aphrody-tauri-runtime-cef", version = "1.4.4" }
|
||||
tokio = { version = "1.53.1", features = ["process", "rt-multi-thread", "macros", "time", "signal", "sync", "fs", "io-util", "full"] }
|
||||
tokio = { version = "1.53.1", features = [
|
||||
"process",
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
"signal",
|
||||
"sync",
|
||||
"fs",
|
||||
"io-util",
|
||||
"full",
|
||||
] }
|
||||
serde = { version = "1.0.229", features = ["derive", "std"], default-features = false }
|
||||
serde_json = "1.0.151"
|
||||
log = "0.4.34"
|
||||
|
||||
+12
-11
@@ -1,4 +1,5 @@
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
|
||||
# aphrody-app — Aphrody Tauri desktop host
|
||||
|
||||
`aphrody-app` is the Tauri 3 (`tauri-runtime-cef`: CEF + tauri-winit + GTK 4) shell in the root Aphrody OS Cargo workspace.
|
||||
@@ -9,11 +10,11 @@ streams. The browser version talks to the same protocol over loopback HTTP/SSE.
|
||||
|
||||
The trusted main window's core commands include:
|
||||
|
||||
| Command | Returns | Use |
|
||||
|---|---|---|
|
||||
| `aphrody_meta()` | `{ app_version, target_os, target_arch, family }` | header / about info |
|
||||
| `aphrody_rpc(request)` | JSON-RPC result | app-server sessions, turns and interruption |
|
||||
| `aphrody_shell_window(action)` | native window state | main-only window controls and navigation |
|
||||
| Command | Returns | Use |
|
||||
| ------------------------------ | ------------------------------------------------- | ------------------------------------------- |
|
||||
| `aphrody_meta()` | `{ app_version, target_os, target_arch, family }` | header / about info |
|
||||
| `aphrody_rpc(request)` | JSON-RPC result | app-server sessions, turns and interruption |
|
||||
| `aphrody_shell_window(action)` | native window state | main-only window controls and navigation |
|
||||
|
||||
The shell command takes its invoking `WebviewWindow`, accepts no target label and
|
||||
rejects every label except `main`. Remote CEF previews have no capability and
|
||||
@@ -35,12 +36,12 @@ not copy or delete them during startup.
|
||||
|
||||
## Runtime boundary
|
||||
|
||||
| Surface | Owner | Contract |
|
||||
|---|---|---|
|
||||
| Agent sessions | `aphrody-app-server` | in-process broker; loopback HTTP/SSE for web clients |
|
||||
| Web tools | Aphrody kernel | `aphrody_web` Obscura/provider subsystem |
|
||||
| Web retrieval and crawl | Aphrody kernel / Obscura | backend capability; no embedded browser or crawler-management view in the public webview |
|
||||
| File libraries | `aphrody-drive` + `sd-daemon` | local socket; daemon stays outside this Cargo graph |
|
||||
| Surface | Owner | Contract |
|
||||
| ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Agent sessions | `aphrody-app-server` | in-process broker; loopback HTTP/SSE for web clients |
|
||||
| Web tools | Aphrody kernel | `aphrody_web` Obscura/provider subsystem |
|
||||
| Web retrieval and crawl | Aphrody kernel / Obscura | backend capability; no embedded browser or crawler-management view in the public webview |
|
||||
| File libraries | `aphrody-drive` + `sd-daemon` | local socket; daemon stays outside this Cargo graph |
|
||||
|
||||
## Capability surface (default-deny)
|
||||
|
||||
|
||||
@@ -1,25 +1,52 @@
|
||||
<!doctype html>
|
||||
<html><body><pre id="out">pending</pre>
|
||||
<script>window.addEventListener("error",e=>{document.title="ERR "+e.message;window.__TAURI__&&window.__TAURI__.core.invoke("report",{text:"WINERROR "+e.message+" "+e.filename})});window.addEventListener("unhandledrejection",e=>{window.__TAURI__&&window.__TAURI__.core.invoke("report",{text:"REJECT "+e.reason})});</script>
|
||||
<script type="module">
|
||||
import init, * as w from "./pkg/aphrody_shell_web.js";
|
||||
import { attach, localTransport, tauriTransport } from "./index.js";
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
try {
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
globalThis.invoke = invoke;
|
||||
await init();
|
||||
const local = localTransport(w, 60, 10);
|
||||
attach(w, local, 60, 10);
|
||||
local.send(new TextEncoder().encode("echo tauri-wasm\r"));
|
||||
const t = tauriTransport({ invoke, listen }, "e2e", 100, 20);
|
||||
const term = attach(w, t, 100, 20);
|
||||
await sleep(800);
|
||||
term.type("echo pty-$((20+22))\r");
|
||||
await sleep(1500);
|
||||
await invoke("report", { text: "LOCAL[" + local.snapshot().lines.join("|") + "] REMOTE[" + term.snapshot().lines.join("|") + "]" });
|
||||
} catch (e) {
|
||||
await globalThis.invoke?.("report", { text: "ERROR " + e + " " + (e && e.stack) });
|
||||
}
|
||||
</script></body></html>
|
||||
<html>
|
||||
<body>
|
||||
<pre id="out">pending</pre>
|
||||
<script>
|
||||
window.addEventListener("error", (e) => {
|
||||
document.title = "ERR " + e.message;
|
||||
window.__TAURI__ &&
|
||||
window.__TAURI__.core.invoke("report", {
|
||||
text: "WINERROR " + e.message + " " + e.filename,
|
||||
});
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (e) => {
|
||||
window.__TAURI__ &&
|
||||
window.__TAURI__.core.invoke("report", {
|
||||
text: "REJECT " + e.reason,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="module">
|
||||
import init, * as w from "./pkg/aphrody_shell_web.js";
|
||||
import { attach, localTransport, tauriTransport } from "./index.js";
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
try {
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
globalThis.invoke = invoke;
|
||||
await init();
|
||||
const local = localTransport(w, 60, 10);
|
||||
attach(w, local, 60, 10);
|
||||
local.send(new TextEncoder().encode("echo tauri-wasm\r"));
|
||||
const t = tauriTransport({ invoke, listen }, "e2e", 100, 20);
|
||||
const term = attach(w, t, 100, 20);
|
||||
await sleep(800);
|
||||
term.type("echo pty-$((20+22))\r");
|
||||
await sleep(1500);
|
||||
await invoke("report", {
|
||||
text:
|
||||
"LOCAL[" +
|
||||
local.snapshot().lines.join("|") +
|
||||
"] REMOTE[" +
|
||||
term.snapshot().lines.join("|") +
|
||||
"]",
|
||||
});
|
||||
} catch (e) {
|
||||
await globalThis.invoke?.("report", {
|
||||
text: "ERROR " + e + " " + (e && e.stack),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/tauri-v3.0.0-alpha.4/crates/tauri-schema-generator/schemas/config.schema.json",
|
||||
"bundle": {
|
||||
"externalBin": [
|
||||
"binaries/sd-daemon",
|
||||
"binaries/aphrody"
|
||||
]
|
||||
"externalBin": ["binaries/sd-daemon", "binaries/aphrody"]
|
||||
}
|
||||
}
|
||||
+12674
-1
File diff suppressed because it is too large.
Load diff
+291027
-507
File diff suppressed because it is too large.
Load diff
@@ -2,15 +2,16 @@
|
||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta charset="utf-8" />
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Aphrody OS</title>
|
||||
<link rel="icon" type="image/svg+xml" href="./fonts/icon.svg">
|
||||
|
||||
|
||||
<link rel="stylesheet" crossorigin href="./app.css"><script type="module" crossorigin src="./app.js"></script></head>
|
||||
<link rel="icon" type="image/svg+xml" href="./fonts/icon.svg" />
|
||||
|
||||
<link rel="stylesheet" crossorigin href="./app.css" />
|
||||
<script type="module" crossorigin src="./app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
[workspace]
|
||||
exclude = ["crates/json-sample-derive", "extensions/*"]
|
||||
members = [
|
||||
# "apps/cloud",
|
||||
# "apps/desktop/crates/*",
|
||||
# "apps/desktop/src-tauri",
|
||||
"apps/cli",
|
||||
# "apps/gpui-photo-grid",
|
||||
"core",
|
||||
"core/benchmarks",
|
||||
"crates/*",
|
||||
"xtask"
|
||||
# "apps/cloud",
|
||||
# "apps/desktop/crates/*",
|
||||
# "apps/desktop/src-tauri",
|
||||
"apps/cli",
|
||||
# "apps/gpui-photo-grid",
|
||||
"core",
|
||||
"core/benchmarks",
|
||||
"crates/*",
|
||||
"xtask",
|
||||
]
|
||||
# The HTTP site is owned by aphrody-web. This nested FSL workspace builds the
|
||||
# Drive backend, daemon and CLI without joining the Apache-2.0 OS workspace.
|
||||
default-members = [
|
||||
"apps/cli",
|
||||
"core",
|
||||
"core/benchmarks",
|
||||
"crates/*",
|
||||
"xtask"
|
||||
]
|
||||
default-members = ["apps/cli", "core", "core/benchmarks", "crates/*", "xtask"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "FSL-1.1-ALv2"
|
||||
repository = "https://github.com/spacedriveapp/spacedrive"
|
||||
edition = "2021"
|
||||
license = "FSL-1.1-ALv2"
|
||||
repository = "https://github.com/spacedriveapp/spacedrive"
|
||||
rust-version = "1.81"
|
||||
|
||||
[workspace.dependencies]
|
||||
@@ -33,64 +27,70 @@ rust-version = "1.81"
|
||||
# sd-cloud-schema = { git = "https://github.com/spacedriveapp/cloud-services-schema", rev = "515ba740ea" }
|
||||
|
||||
# Third party dependencies used by one or more of our crates
|
||||
anyhow = "1.0.94"
|
||||
async-channel = "2.3"
|
||||
async-stream = "0.3.6"
|
||||
async-trait = "0.1.83"
|
||||
axum = "0.7.9"
|
||||
axum-extra = "0.9.6"
|
||||
base64 = "0.22.1"
|
||||
blake3 = "1.5.5"
|
||||
bytes = "1.9.0"
|
||||
chrono = "0.4.39"
|
||||
ed25519-dalek = "2.1"
|
||||
flume = "0.11.0"
|
||||
futures = "0.3.31"
|
||||
anyhow = "1.0.94"
|
||||
async-channel = "2.3"
|
||||
async-stream = "0.3.6"
|
||||
async-trait = "0.1.83"
|
||||
axum = "0.7.9"
|
||||
axum-extra = "0.9.6"
|
||||
base64 = "0.22.1"
|
||||
blake3 = "1.5.5"
|
||||
bytes = "1.9.0"
|
||||
chrono = "0.4.39"
|
||||
ed25519-dalek = "2.1"
|
||||
flume = "0.11.0"
|
||||
futures = "0.3.31"
|
||||
futures-concurrency = "7.6.2"
|
||||
globset = "0.4.15"
|
||||
http = "1.2.0"
|
||||
hyper = "1.5.2"
|
||||
image = "0.25.5"
|
||||
iroh = "0.95.1"
|
||||
itertools = "0.13.0"
|
||||
lending-stream = "1.0"
|
||||
libc = "0.2.169"
|
||||
mimalloc = "0.1.43"
|
||||
normpath = "1.3"
|
||||
pin-project-lite = "0.2.15"
|
||||
quic-rpc = "0.17.3"
|
||||
rand = "0.9.0-alpha.2"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.9", default-features = false }
|
||||
rmp = "0.8.14"
|
||||
rmp-serde = "1.3"
|
||||
rmpv = { version = "1.3", features = ["with-serde"] }
|
||||
serde = "1.0.216"
|
||||
serde_json = "1.0.133"
|
||||
specta = { git = "https://github.com/jamiepine/specta", branch = "main" }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
tempfile = "3.14.0"
|
||||
thiserror = "2.0.8"
|
||||
tokio = "1.42.0"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.13"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-test = "0.2.5"
|
||||
uhlc = "0.8.0" # Must follow version used by specta
|
||||
uuid = "1.10" # Must follow version used by specta
|
||||
webp = "0.3.0"
|
||||
zeroize = "1.8"
|
||||
sqlx = { version = "0.8", default-features = false, features = ["sqlite", "runtime-tokio"] }
|
||||
lancedb = "0.15"
|
||||
fastembed = "4"
|
||||
arrow-array = "53.3"
|
||||
arrow-schema = "53.3"
|
||||
indexmap = "2.7"
|
||||
dashmap = "6.1"
|
||||
toml = "0.8"
|
||||
ort = { version = "2.0.0-rc.9", default-features = false, features = ["download-binaries", "tls-native"] }
|
||||
globset = "0.4.15"
|
||||
http = "1.2.0"
|
||||
hyper = "1.5.2"
|
||||
image = "0.25.5"
|
||||
iroh = "0.95.1"
|
||||
itertools = "0.13.0"
|
||||
lending-stream = "1.0"
|
||||
libc = "0.2.169"
|
||||
mimalloc = "0.1.43"
|
||||
normpath = "1.3"
|
||||
pin-project-lite = "0.2.15"
|
||||
quic-rpc = "0.17.3"
|
||||
rand = "0.9.0-alpha.2"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.9", default-features = false }
|
||||
rmp = "0.8.14"
|
||||
rmp-serde = "1.3"
|
||||
rmpv = { version = "1.3", features = ["with-serde"] }
|
||||
serde = "1.0.216"
|
||||
serde_json = "1.0.133"
|
||||
specta = { git = "https://github.com/jamiepine/specta", branch = "main" }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
tempfile = "3.14.0"
|
||||
thiserror = "2.0.8"
|
||||
tokio = "1.42.0"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.13"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-test = "0.2.5"
|
||||
uhlc = "0.8.0" # Must follow version used by specta
|
||||
uuid = "1.10" # Must follow version used by specta
|
||||
webp = "0.3.0"
|
||||
zeroize = "1.8"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"sqlite",
|
||||
"runtime-tokio",
|
||||
] }
|
||||
lancedb = "0.15"
|
||||
fastembed = "4"
|
||||
arrow-array = "53.3"
|
||||
arrow-schema = "53.3"
|
||||
indexmap = "2.7"
|
||||
dashmap = "6.1"
|
||||
toml = "0.8"
|
||||
ort = { version = "2.0.0-rc.9", default-features = false, features = [
|
||||
"download-binaries",
|
||||
"tls-native",
|
||||
] }
|
||||
|
||||
# Proper IOS Support
|
||||
# TODO: Fix the fork - it has compilation errors with TimerFuture
|
||||
@@ -102,22 +102,22 @@ ort = { version = "2.0.0-rc.9", default-features = false, featur
|
||||
|
||||
[profile.dev]
|
||||
# Make compilation faster on macOS
|
||||
codegen-units = 256
|
||||
debug = 0
|
||||
incremental = true
|
||||
lto = false
|
||||
opt-level = 0
|
||||
codegen-units = 256
|
||||
debug = 0
|
||||
incremental = true
|
||||
lto = false
|
||||
opt-level = 0
|
||||
split-debuginfo = "unpacked"
|
||||
strip = "none"
|
||||
strip = "none"
|
||||
|
||||
[profile.dev-debug]
|
||||
inherits = "dev"
|
||||
# Enables debugger
|
||||
codegen-units = 256
|
||||
debug = "full"
|
||||
incremental = true
|
||||
lto = "off"
|
||||
opt-level = 0
|
||||
debug = "full"
|
||||
incremental = true
|
||||
lto = "off"
|
||||
opt-level = 0
|
||||
# split-debuginfo = "none"
|
||||
strip = "none"
|
||||
|
||||
@@ -128,22 +128,22 @@ opt-level = 3
|
||||
# Set the default for dependencies, except workspace members.
|
||||
[profile.dev.package."*"]
|
||||
incremental = false
|
||||
opt-level = 3
|
||||
opt-level = 3
|
||||
|
||||
# Set the default for dependencies, except workspace members.
|
||||
[profile.dev-debug.package."*"]
|
||||
debug = "full"
|
||||
debug = "full"
|
||||
incremental = false
|
||||
inherits = "dev"
|
||||
opt-level = 3
|
||||
inherits = "dev"
|
||||
opt-level = 3
|
||||
|
||||
# Optimize release builds
|
||||
[profile.release]
|
||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||
lto = true # Enables link to optimizations
|
||||
opt-level = "s" # Optimize for binary size
|
||||
panic = "unwind" # Sadly we need unwind to avoid unexpected crashes on third party crates
|
||||
strip = true # Remove debug symbols
|
||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||
lto = true # Enables link to optimizations
|
||||
opt-level = "s" # Optimize for binary size
|
||||
panic = "unwind" # Sadly we need unwind to avoid unexpected crashes on third party crates
|
||||
strip = true # Remove debug symbols
|
||||
|
||||
# Aphrody local patch: ffmpeg-sys-next 7.1.3 built with bindgen 0.72 (0.70 emits empty structs
|
||||
# with libclang 21+). See patches/README.md.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!-- SPDX-License-Identifier: FSL-1.1-ALv2 -->
|
||||
|
||||
# Drive backend migration workspace
|
||||
|
||||
This nested Cargo workspace contains the additive OS-owned copy of the
|
||||
|
||||
@@ -18,8 +18,8 @@ requires = ["python3 >= 3.9"]
|
||||
[adapter.oauth]
|
||||
token_url = "https://oauth2.googleapis.com/token"
|
||||
scopes = [
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.metadata",
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.metadata",
|
||||
]
|
||||
token_key = "oauth_token"
|
||||
|
||||
|
||||
@@ -48,5 +48,12 @@ primary_model = "contact"
|
||||
title = "name"
|
||||
preview = "notes"
|
||||
subtitle = "organization"
|
||||
search_fields = ["name", "organization", "job_title", "emails", "phones", "notes"]
|
||||
search_fields = [
|
||||
"name",
|
||||
"organization",
|
||||
"job_title",
|
||||
"emails",
|
||||
"phones",
|
||||
"notes",
|
||||
]
|
||||
date_field = "modified"
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "sd-cli"
|
||||
name = "sd-cli"
|
||||
version = "2.0.0-alpha.2"
|
||||
|
||||
[features]
|
||||
@@ -12,20 +12,20 @@ speech-to-text = ["sd-core/speech-to-text"]
|
||||
ai = ["sd-core/ai"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
comfy-table = "7.1"
|
||||
crossterm = "0.27"
|
||||
dirs = "5.0"
|
||||
indicatif = "0.17"
|
||||
inquire = "0.7"
|
||||
qr2term = "0.3"
|
||||
ratatui = "0.26"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
sd-core = { path = "../../core", features = ["cli"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
crossterm = "0.27"
|
||||
dirs = "5.0"
|
||||
indicatif = "0.17"
|
||||
inquire = "0.7"
|
||||
qr2term = "0.3"
|
||||
ratatui = "0.26"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
sd-core = { path = "../../core", features = ["cli"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
+106
-102
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "sd-core"
|
||||
name = "sd-core"
|
||||
version = "2.0.0-alpha.2"
|
||||
autobins = true
|
||||
|
||||
@@ -25,20 +25,23 @@ wasm = ["dep:wasmer", "dep:wasmer-middlewares"]
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
async-channel = { workspace = true }
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
|
||||
# Database
|
||||
sea-orm = { version = "1.1", features = [
|
||||
"macros",
|
||||
"runtime-tokio-rustls",
|
||||
"sqlx-sqlite",
|
||||
"uuid",
|
||||
"with-chrono",
|
||||
"with-json"
|
||||
"macros",
|
||||
"runtime-tokio-rustls",
|
||||
"sqlx-sqlite",
|
||||
"uuid",
|
||||
"with-chrono",
|
||||
"with-json",
|
||||
] }
|
||||
sea-orm-migration = { version = "1.1", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlx-sqlite",
|
||||
] }
|
||||
sea-orm-migration = { version = "1.1", features = ["runtime-tokio-rustls", "sqlx-sqlite"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
|
||||
# API (temporarily disabled)
|
||||
@@ -51,14 +54,7 @@ serde_json = "1.0"
|
||||
|
||||
# HTTP client (for model downloads)
|
||||
reqwest = { version = "0.11", features = ["stream"] }
|
||||
specta = { git = "https://github.com/jamiepine/specta", branch = "main", features = [
|
||||
"chrono",
|
||||
"derive",
|
||||
"export",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid"
|
||||
] }
|
||||
specta = { git = "https://github.com/jamiepine/specta", branch = "main", features = ["chrono", "derive", "export", "serde", "serde_json", "uuid"] }
|
||||
specta-serde = { git = "https://github.com/jamiepine/specta", branch = "main" }
|
||||
specta-swift = { git = "https://github.com/jamiepine/specta", branch = "main" }
|
||||
specta-typescript = { git = "https://github.com/jamiepine/specta", branch = "main" }
|
||||
@@ -68,7 +64,7 @@ hostname = "^0.4"
|
||||
|
||||
|
||||
# Error handling
|
||||
anyhow = "1.0"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
# Text processing
|
||||
@@ -76,41 +72,41 @@ regex = "1.11"
|
||||
|
||||
|
||||
# File operations
|
||||
blake3 = "1.5" # Content addressing
|
||||
bytes = "1.7" # Efficient byte buffers for I/O operations
|
||||
hex = "0.4" # Hex encoding for volume fingerprints
|
||||
notify = "6.1" # File system watching
|
||||
sha2 = "0.10" # SHA-256 hashing for CAS IDs
|
||||
blake3 = "1.5" # Content addressing
|
||||
bytes = "1.7" # Efficient byte buffers for I/O operations
|
||||
hex = "0.4" # Hex encoding for volume fingerprints
|
||||
notify = "6.1" # File system watching
|
||||
sha2 = "0.10" # SHA-256 hashing for CAS IDs
|
||||
|
||||
# Cloud storage integration
|
||||
opendal = { version = "0.54", features = [
|
||||
"services-s3",
|
||||
"services-gdrive",
|
||||
"services-onedrive",
|
||||
"services-dropbox",
|
||||
"services-azblob",
|
||||
"services-gcs",
|
||||
"services-s3",
|
||||
"services-gdrive",
|
||||
"services-onedrive",
|
||||
"services-dropbox",
|
||||
"services-azblob",
|
||||
"services-gcs",
|
||||
] }
|
||||
|
||||
# Logging
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# WASM Plugin System
|
||||
wasmer = { version = "4.2", optional = true }
|
||||
wasmer = { version = "4.2", optional = true }
|
||||
wasmer-middlewares = { version = "4.2", optional = true }
|
||||
|
||||
# Indexer rules engine
|
||||
futures-concurrency = "7.6"
|
||||
gix-ignore = { version = "0.11", features = ["serde"] }
|
||||
globset = { version = "0.4", features = ["serde1"] }
|
||||
gix-ignore = { version = "0.11", features = ["serde"] }
|
||||
globset = { version = "0.4", features = ["serde1"] }
|
||||
|
||||
# Job system dependencies
|
||||
inventory = "0.3" # Automatic job registration
|
||||
job-derive = { path = "../crates/job-derive" } # Job derive macros
|
||||
rmp = "0.8" # MessagePack core types
|
||||
rmp-serde = "1.3" # MessagePack serialization for job state
|
||||
inventory = "0.3" # Automatic job registration
|
||||
job-derive = { path = "../crates/job-derive" } # Job derive macros
|
||||
rmp = "0.8" # MessagePack core types
|
||||
rmp-serde = "1.3" # MessagePack serialization for job state
|
||||
sd-task-system = { path = "../crates/task-system" }
|
||||
|
||||
# Archive system - indexes external data sources (emails, notes, etc.)
|
||||
@@ -120,19 +116,19 @@ sd-archive = { path = "../crates/archive" }
|
||||
# lancedb = "0.15" # Embedded vector database (conflicts with gpui)
|
||||
|
||||
# Media processing dependencies
|
||||
blurhash = "0.2"
|
||||
image = "0.25"
|
||||
sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
|
||||
sd-fs-watcher = { path = "../crates/fs-watcher" }
|
||||
sd-images = { path = "../crates/images" }
|
||||
blurhash = "0.2"
|
||||
image = "0.25"
|
||||
sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
|
||||
sd-fs-watcher = { path = "../crates/fs-watcher" }
|
||||
sd-images = { path = "../crates/images" }
|
||||
sd-media-metadata = { path = "../crates/media-metadata" }
|
||||
tokio-rustls = "0.26"
|
||||
webp = "0.3"
|
||||
tokio-rustls = "0.26"
|
||||
webp = "0.3"
|
||||
|
||||
# Speech-to-text dependencies (optional, behind whisper feature)
|
||||
whisper-rs = { version = "0.16", optional = true }
|
||||
hound = { version = "3.5", optional = true } # WAV file reading
|
||||
rubato = { version = "0.16", optional = true } # Audio resampling to 16kHz
|
||||
hound = { version = "3.5", optional = true } # WAV file reading
|
||||
rubato = { version = "0.16", optional = true } # Audio resampling to 16kHz
|
||||
|
||||
# Networking
|
||||
# Iroh P2P networking
|
||||
@@ -145,73 +141,73 @@ serde_cbor = "0.11"
|
||||
ed25519-dalek = "2.1"
|
||||
|
||||
# Cryptography and networking utilities
|
||||
aes-gcm = "0.10" # AES-GCM encryption for secure storage
|
||||
argon2 = "0.5" # Password derivation
|
||||
async-stream = "0.3" # File streaming
|
||||
backoff = "0.4" # Retry logic
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] } # Efficient encoding with serde compat
|
||||
ring = "0.16" # Crypto primitives
|
||||
tokio-stream = "0.1" # Async streams
|
||||
aes-gcm = "0.10" # AES-GCM encryption for secure storage
|
||||
argon2 = "0.5" # Password derivation
|
||||
async-stream = "0.3" # File streaming
|
||||
backoff = "0.4" # Retry logic
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] } # Efficient encoding with serde compat
|
||||
ring = "0.16" # Crypto primitives
|
||||
tokio-stream = "0.1" # Async streams
|
||||
|
||||
# BIP39 wordlist support
|
||||
bip39 = "2.0"
|
||||
|
||||
# Additional cryptography
|
||||
chacha20poly1305 = "0.10" # Authenticated encryption for chunk-level security
|
||||
hkdf = "0.12" # Key derivation function for session keys
|
||||
hmac = "0.12"
|
||||
x25519-dalek = "2.0"
|
||||
hkdf = "0.12" # Key derivation function for session keys
|
||||
hmac = "0.12"
|
||||
x25519-dalek = "2.0"
|
||||
|
||||
# Network utilities
|
||||
if-watch = "3.0"
|
||||
if-watch = "3.0"
|
||||
local-ip-address = "0.5"
|
||||
# colored already defined above
|
||||
|
||||
# Utils
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dirs = "5.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dirs = "5.0"
|
||||
once_cell = "1.20"
|
||||
rand = "0.8" # Random number generation for secure delete
|
||||
sysinfo = "0.31" # Cross-platform system information
|
||||
tempfile = "3.14" # Temporary directories for testing
|
||||
uuid = { version = "1.11", features = ["serde", "v4", "v5", "v7"] }
|
||||
rand = "0.8" # Random number generation for secure delete
|
||||
sysinfo = "0.31" # Cross-platform system information
|
||||
tempfile = "3.14" # Temporary directories for testing
|
||||
uuid = { version = "1.11", features = ["serde", "v4", "v5", "v7"] }
|
||||
|
||||
# High-performance ephemeral index
|
||||
memmap2 = "0.9" # Memory-mapped file support for arena storage
|
||||
smallvec = { version = "1.13", features = ["serde"] } # Small vector optimization for children arrays
|
||||
parking_lot = "0.12" # Fast mutex for name cache
|
||||
num_cpus = "1.16" # CPU count for parallel walker
|
||||
postcard = { version = "1", features = ["use-std"] } # Zero-copy binary serialization for snapshots
|
||||
zstd = { version = "0.13", features = ["zstdmt"] } # Multithreaded compression for snapshots
|
||||
whoami = "1.5"
|
||||
memmap2 = "0.9" # Memory-mapped file support for arena storage
|
||||
smallvec = { version = "1.13", features = ["serde"] } # Small vector optimization for children arrays
|
||||
parking_lot = "0.12" # Fast mutex for name cache
|
||||
num_cpus = "1.16" # CPU count for parallel walker
|
||||
postcard = { version = "1", features = ["use-std"] } # Zero-copy binary serialization for snapshots
|
||||
zstd = { version = "0.13", features = ["zstdmt"] } # Multithreaded compression for snapshots
|
||||
whoami = "1.5"
|
||||
|
||||
# Secure storage
|
||||
keyring = "3.6" # Only for device key in keychain
|
||||
redb = "2.2" # Encrypted KV store for library keys, credentials, etc.
|
||||
keyring = "3.6" # Only for device key in keychain
|
||||
redb = "2.2" # Encrypted KV store for library keys, credentials, etc.
|
||||
|
||||
# CLI dependencies
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
colored = "2.1"
|
||||
comfy-table = "7.1"
|
||||
console = "0.15"
|
||||
dialoguer = "0.11"
|
||||
indicatif = "0.17"
|
||||
owo-colors = "4.1"
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
colored = "2.1"
|
||||
comfy-table = "7.1"
|
||||
console = "0.15"
|
||||
dialoguer = "0.11"
|
||||
indicatif = "0.17"
|
||||
owo-colors = "4.1"
|
||||
supports-color = "3.0"
|
||||
|
||||
[build-dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
colored = "2.1"
|
||||
comfy-table = "7.1"
|
||||
console = "0.15"
|
||||
crossterm = "0.28"
|
||||
dialoguer = "0.11"
|
||||
indicatif = "0.17"
|
||||
owo-colors = "4.1"
|
||||
ratatui = "0.29"
|
||||
serde_json = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
colored = "2.1"
|
||||
comfy-table = "7.1"
|
||||
console = "0.15"
|
||||
crossterm = "0.28"
|
||||
dialoguer = "0.11"
|
||||
indicatif = "0.17"
|
||||
owo-colors = "4.1"
|
||||
ratatui = "0.29"
|
||||
serde_json = "1.0"
|
||||
supports-color = "3.0"
|
||||
vergen = { version = "8", features = ["cargo", "git", "gitcl"] }
|
||||
vergen = { version = "8", features = ["cargo", "git", "gitcl"] }
|
||||
|
||||
# Platform specific
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
@@ -221,7 +217,15 @@ libc = "0.2"
|
||||
trash = "3.3" # Native trash/recycle bin support on desktop platforms
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.52", features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Security", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Power"] }
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
"Win32_System_Ioctl",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_Registry",
|
||||
"Win32_System_Power",
|
||||
] }
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
|
||||
whisper-rs = { version = "0.16", features = ["metal"], optional = true }
|
||||
@@ -230,13 +234,13 @@ whisper-rs = { version = "0.16", features = ["metal"], optional = true }
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = { version = "0.3", features = [
|
||||
"NSFileManager",
|
||||
"NSURL",
|
||||
"NSString",
|
||||
"NSArray",
|
||||
"NSValue",
|
||||
"NSDictionary",
|
||||
"NSError",
|
||||
"NSFileManager",
|
||||
"NSURL",
|
||||
"NSString",
|
||||
"NSArray",
|
||||
"NSValue",
|
||||
"NSDictionary",
|
||||
"NSError",
|
||||
] }
|
||||
objc2-ui-kit = { version = "0.3", features = ["UIDevice"] }
|
||||
|
||||
@@ -251,4 +255,4 @@ path = "src/bin/daemon.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4"
|
||||
tempfile = "3.14"
|
||||
tempfile = "3.14"
|
||||
@@ -1,32 +1,34 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "sd-bench"
|
||||
name = "sd-bench"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
blake3 = "1.5"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
dirs = "5.0"
|
||||
humantime = "2.1"
|
||||
humantime-serde = "1.1"
|
||||
indicatif = "0.17"
|
||||
rand = "0.8"
|
||||
regex = "1.10"
|
||||
sd-core = { path = ".." }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_with = { version = "3.9", features = ["json"] }
|
||||
serde_yaml = "0.9"
|
||||
sysinfo = { version = "0.30", default-features = false, features = ["multithread"] }
|
||||
tempfile = "3.14"
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
blake3 = "1.5"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
dirs = "5.0"
|
||||
humantime = "2.1"
|
||||
humantime-serde = "1.1"
|
||||
indicatif = "0.17"
|
||||
rand = "0.8"
|
||||
regex = "1.10"
|
||||
sd-core = { path = ".." }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_with = { version = "3.9", features = ["json"] }
|
||||
serde_yaml = "0.9"
|
||||
sysinfo = { version = "0.30", default-features = false, features = [
|
||||
"multithread",
|
||||
] }
|
||||
tempfile = "3.14"
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uuid = { version = "1.11", features = ["serde", "v4"] }
|
||||
walkdir = "2.5"
|
||||
uuid = { version = "1.11", features = ["serde", "v4"] }
|
||||
walkdir = "2.5"
|
||||
|
||||
[lib]
|
||||
name = "sd_bench"
|
||||
|
||||
@@ -3,36 +3,99 @@ seed: 42424242
|
||||
locations:
|
||||
- path: benchdata/desktop_complex
|
||||
structure:
|
||||
depth: 8 # Much deeper nesting like real file systems (Documents/Projects/2024/Client/Assets/Images/Raw/...)
|
||||
fanout_per_dir: 25 # More directories per level
|
||||
depth: 8 # Much deeper nesting like real file systems (Documents/Projects/2024/Client/Assets/Images/Raw/...)
|
||||
fanout_per_dir: 25 # More directories per level
|
||||
files:
|
||||
total: 50000 # 50k files - realistic but manageable size (~2-5GB total)
|
||||
total: 50000 # 50k files - realistic but manageable size (~2-5GB total)
|
||||
size_buckets:
|
||||
# Toned down but still realistic distribution
|
||||
tiny: { range: [0, 4096], share: 0.30 } # Config files, text files, small scripts
|
||||
small: { range: [4096, 1048576], share: 0.40 } # Documents, code files, small images
|
||||
medium: { range: [1048576, 10000000], share: 0.25 } # Photos, music, small videos (max 10MB)
|
||||
large: { range: [10000000, 100000000], share: 0.05 } # Large videos, archives (max 100MB)
|
||||
tiny: { range: [0, 4096], share: 0.30 } # Config files, text files, small scripts
|
||||
small: { range: [4096, 1048576], share: 0.40 } # Documents, code files, small images
|
||||
medium: { range: [1048576, 10000000], share: 0.25 } # Photos, music, small videos (max 10MB)
|
||||
large: { range: [10000000, 100000000], share: 0.05 } # Large videos, archives (max 100MB)
|
||||
extensions: [
|
||||
# Documents
|
||||
txt, md, pdf, doc, docx, xls, xlsx, ppt, pptx, rtf, odt,
|
||||
# Images
|
||||
jpg, jpeg, png, gif, bmp, tiff, webp, svg, raw, cr2, nef, dng,
|
||||
# Videos
|
||||
mp4, mkv, avi, mov, wmv, flv, webm, m4v,
|
||||
# Audio
|
||||
mp3, flac, wav, aac, ogg, m4a, wma,
|
||||
# Code
|
||||
js, ts, py, rs, go, java, cpp, c, h, css, html, json, xml, yaml, toml,
|
||||
# Archives
|
||||
zip, rar, 7z, tar, gz, bz2, xz,
|
||||
# Other
|
||||
exe, dmg, pkg, deb, rpm, iso, bin, log, db, sqlite
|
||||
]
|
||||
duplicate_ratio: 0.15 # Higher duplicate ratio - more realistic for desktops
|
||||
# Documents
|
||||
txt,
|
||||
md,
|
||||
pdf,
|
||||
doc,
|
||||
docx,
|
||||
xls,
|
||||
xlsx,
|
||||
ppt,
|
||||
pptx,
|
||||
rtf,
|
||||
odt,
|
||||
# Images
|
||||
jpg,
|
||||
jpeg,
|
||||
png,
|
||||
gif,
|
||||
bmp,
|
||||
tiff,
|
||||
webp,
|
||||
svg,
|
||||
raw,
|
||||
cr2,
|
||||
nef,
|
||||
dng,
|
||||
# Videos
|
||||
mp4,
|
||||
mkv,
|
||||
avi,
|
||||
mov,
|
||||
wmv,
|
||||
flv,
|
||||
webm,
|
||||
m4v,
|
||||
# Audio
|
||||
mp3,
|
||||
flac,
|
||||
wav,
|
||||
aac,
|
||||
ogg,
|
||||
m4a,
|
||||
wma,
|
||||
# Code
|
||||
js,
|
||||
ts,
|
||||
py,
|
||||
rs,
|
||||
go,
|
||||
java,
|
||||
cpp,
|
||||
c,
|
||||
h,
|
||||
css,
|
||||
html,
|
||||
json,
|
||||
xml,
|
||||
yaml,
|
||||
toml,
|
||||
# Archives
|
||||
zip,
|
||||
rar,
|
||||
7z,
|
||||
tar,
|
||||
gz,
|
||||
bz2,
|
||||
xz,
|
||||
# Other
|
||||
exe,
|
||||
dmg,
|
||||
pkg,
|
||||
deb,
|
||||
rpm,
|
||||
iso,
|
||||
bin,
|
||||
log,
|
||||
db,
|
||||
sqlite,
|
||||
]
|
||||
duplicate_ratio: 0.15 # Higher duplicate ratio - more realistic for desktops
|
||||
content_gen:
|
||||
mode: partial # Faster generation while still providing realistic content hashing
|
||||
mode: partial # Faster generation while still providing realistic content hashing
|
||||
sample_block_size: 10240
|
||||
magic_headers: true
|
||||
media:
|
||||
generate_thumbnails: false # Keep generation fast for testing
|
||||
generate_thumbnails: false # Keep generation fast for testing
|
||||
@@ -3,35 +3,177 @@ seed: 99999999
|
||||
locations:
|
||||
- path: benchdata/desktop_extreme
|
||||
structure:
|
||||
depth: 12 # Very deep nesting like real complex file systems
|
||||
fanout_per_dir: 30 # Many directories per level
|
||||
depth: 12 # Very deep nesting like real complex file systems
|
||||
fanout_per_dir: 30 # Many directories per level
|
||||
files:
|
||||
total: 100000 # 100k files - still complex but manageable (~5-10GB total)
|
||||
total: 100000 # 100k files - still complex but manageable (~5-10GB total)
|
||||
size_buckets:
|
||||
# Realistic but toned down distribution
|
||||
tiny: { range: [0, 1024], share: 0.35 } # Lots of small config/cache files
|
||||
small: { range: [1024, 65536], share: 0.30 } # Text files, small images, code
|
||||
medium: { range: [65536, 5000000], share: 0.25 } # Documents, photos, music (max 5MB)
|
||||
large: { range: [5000000, 200000000], share: 0.10 } # Videos, large archives (max 200MB)
|
||||
tiny: { range: [0, 1024], share: 0.35 } # Lots of small config/cache files
|
||||
small: { range: [1024, 65536], share: 0.30 } # Text files, small images, code
|
||||
medium: { range: [65536, 5000000], share: 0.25 } # Documents, photos, music (max 5MB)
|
||||
large: { range: [5000000, 200000000], share: 0.10 } # Videos, large archives (max 200MB)
|
||||
extensions: [
|
||||
# Comprehensive file type list for realistic desktop
|
||||
txt, md, pdf, doc, docx, xls, xlsx, ppt, pptx, rtf, odt, pages, numbers, keynote,
|
||||
jpg, jpeg, png, gif, bmp, tiff, webp, svg, raw, cr2, nef, dng, arw, orf, rw2, psd, ai, eps,
|
||||
mp4, mkv, avi, mov, wmv, flv, webm, m4v, mpg, mpeg, 3gp, f4v, asf, rm, rmvb,
|
||||
mp3, flac, wav, aac, ogg, m4a, wma, ape, opus, ac3, dts,
|
||||
js, ts, jsx, tsx, py, rs, go, java, cpp, c, h, hpp, cs, php, rb, swift, kt, dart, scala,
|
||||
css, scss, sass, less, html, htm, xml, json, yaml, yml, toml, ini, cfg, conf,
|
||||
zip, rar, 7z, tar, gz, bz2, xz, lz4, zst, cab, ace, arj,
|
||||
exe, msi, dmg, pkg, deb, rpm, snap, appimage, flatpak, iso, img, bin, app,
|
||||
log, db, sqlite, sql, csv, tsv, parquet, avro, proto, thrift,
|
||||
ttf, otf, woff, woff2, eot, fon, pfb, pfm,
|
||||
blend, fbx, obj, dae, 3ds, max, ma, mb, c4d, skp, dwg, dxf,
|
||||
sketch, fig, xd, indd, qxd, afdesign, afphoto, afpub
|
||||
]
|
||||
duplicate_ratio: 0.20 # High duplicate ratio - very realistic for desktops with backups/copies
|
||||
# Comprehensive file type list for realistic desktop
|
||||
txt,
|
||||
md,
|
||||
pdf,
|
||||
doc,
|
||||
docx,
|
||||
xls,
|
||||
xlsx,
|
||||
ppt,
|
||||
pptx,
|
||||
rtf,
|
||||
odt,
|
||||
pages,
|
||||
numbers,
|
||||
keynote,
|
||||
jpg,
|
||||
jpeg,
|
||||
png,
|
||||
gif,
|
||||
bmp,
|
||||
tiff,
|
||||
webp,
|
||||
svg,
|
||||
raw,
|
||||
cr2,
|
||||
nef,
|
||||
dng,
|
||||
arw,
|
||||
orf,
|
||||
rw2,
|
||||
psd,
|
||||
ai,
|
||||
eps,
|
||||
mp4,
|
||||
mkv,
|
||||
avi,
|
||||
mov,
|
||||
wmv,
|
||||
flv,
|
||||
webm,
|
||||
m4v,
|
||||
mpg,
|
||||
mpeg,
|
||||
3gp,
|
||||
f4v,
|
||||
asf,
|
||||
rm,
|
||||
rmvb,
|
||||
mp3,
|
||||
flac,
|
||||
wav,
|
||||
aac,
|
||||
ogg,
|
||||
m4a,
|
||||
wma,
|
||||
ape,
|
||||
opus,
|
||||
ac3,
|
||||
dts,
|
||||
js,
|
||||
ts,
|
||||
jsx,
|
||||
tsx,
|
||||
py,
|
||||
rs,
|
||||
go,
|
||||
java,
|
||||
cpp,
|
||||
c,
|
||||
h,
|
||||
hpp,
|
||||
cs,
|
||||
php,
|
||||
rb,
|
||||
swift,
|
||||
kt,
|
||||
dart,
|
||||
scala,
|
||||
css,
|
||||
scss,
|
||||
sass,
|
||||
less,
|
||||
html,
|
||||
htm,
|
||||
xml,
|
||||
json,
|
||||
yaml,
|
||||
yml,
|
||||
toml,
|
||||
ini,
|
||||
cfg,
|
||||
conf,
|
||||
zip,
|
||||
rar,
|
||||
7z,
|
||||
tar,
|
||||
gz,
|
||||
bz2,
|
||||
xz,
|
||||
lz4,
|
||||
zst,
|
||||
cab,
|
||||
ace,
|
||||
arj,
|
||||
exe,
|
||||
msi,
|
||||
dmg,
|
||||
pkg,
|
||||
deb,
|
||||
rpm,
|
||||
snap,
|
||||
appimage,
|
||||
flatpak,
|
||||
iso,
|
||||
img,
|
||||
bin,
|
||||
app,
|
||||
log,
|
||||
db,
|
||||
sqlite,
|
||||
sql,
|
||||
csv,
|
||||
tsv,
|
||||
parquet,
|
||||
avro,
|
||||
proto,
|
||||
thrift,
|
||||
ttf,
|
||||
otf,
|
||||
woff,
|
||||
woff2,
|
||||
eot,
|
||||
fon,
|
||||
pfb,
|
||||
pfm,
|
||||
blend,
|
||||
fbx,
|
||||
obj,
|
||||
dae,
|
||||
3ds,
|
||||
max,
|
||||
ma,
|
||||
mb,
|
||||
c4d,
|
||||
skp,
|
||||
dwg,
|
||||
dxf,
|
||||
sketch,
|
||||
fig,
|
||||
xd,
|
||||
indd,
|
||||
qxd,
|
||||
afdesign,
|
||||
afphoto,
|
||||
afpub,
|
||||
]
|
||||
duplicate_ratio: 0.20 # High duplicate ratio - very realistic for desktops with backups/copies
|
||||
content_gen:
|
||||
mode: partial # Keep generation reasonable while providing realistic content
|
||||
mode: partial # Keep generation reasonable while providing realistic content
|
||||
sample_block_size: 10240
|
||||
magic_headers: true
|
||||
media:
|
||||
generate_thumbnails: false # Keep generation fast for testing
|
||||
generate_thumbnails: false # Keep generation fast for testing
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "f01681c8-bc82-4e05-a26e-eeaedc3e1239",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_large"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_large"],
|
||||
"recipe_name": "shape_large",
|
||||
"timestamp_utc": "2025-08-10T09:46:25.346840+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+29
-31
@@ -1,33 +1,31 @@
|
||||
{
|
||||
"runs": [
|
||||
{
|
||||
"dirs": 16507,
|
||||
"dirs_per_s": 5.647859855612961,
|
||||
"durations": {
|
||||
"content_s": 2922.7,
|
||||
"discovery_s": 2990.7,
|
||||
"processing_s": 2984.5,
|
||||
"total_s": 2922.7
|
||||
},
|
||||
"errors": 0,
|
||||
"files": 100000,
|
||||
"files_per_s": 34.214938,
|
||||
"meta": {
|
||||
"hardware_label": "External HDD (Seagate)",
|
||||
"host": {
|
||||
"cpu_model": "Apple M3 Max",
|
||||
"cpu_physical_cores": 16,
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "f84809a7-b9bd-4647-b724-d837643831d7",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_large"
|
||||
],
|
||||
"recipe_name": "shape_large",
|
||||
"timestamp_utc": "2025-08-10T10:36:35.604531+00:00"
|
||||
},
|
||||
"scenario": "content-identification",
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
"runs": [
|
||||
{
|
||||
"dirs": 16507,
|
||||
"dirs_per_s": 5.647859855612961,
|
||||
"durations": {
|
||||
"content_s": 2922.7,
|
||||
"discovery_s": 2990.7,
|
||||
"processing_s": 2984.5,
|
||||
"total_s": 2922.7
|
||||
},
|
||||
"errors": 0,
|
||||
"files": 100000,
|
||||
"files_per_s": 34.214938,
|
||||
"meta": {
|
||||
"hardware_label": "External HDD (Seagate)",
|
||||
"host": {
|
||||
"cpu_model": "Apple M3 Max",
|
||||
"cpu_physical_cores": 16,
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "f84809a7-b9bd-4647-b724-d837643831d7",
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_large"],
|
||||
"recipe_name": "shape_large",
|
||||
"timestamp_utc": "2025-08-10T10:36:35.604531+00:00"
|
||||
},
|
||||
"scenario": "content-identification",
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -30,4 +30,4 @@
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "8085806f-d7e3-4cfc-9c66-9b0ec782e3be",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_large"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_large"],
|
||||
"recipe_name": "shape_large",
|
||||
"timestamp_utc": "2025-08-10T09:42:47.682209+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 11882.38
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "d209e68e-f366-44cb-8249-d6fecc68e6d8",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_medium"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_medium"],
|
||||
"recipe_name": "shape_medium",
|
||||
"timestamp_utc": "2025-08-10T09:46:36.101659+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "209bd316-ca8e-472e-995b-723b94a5b666",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_medium"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_medium"],
|
||||
"recipe_name": "shape_medium",
|
||||
"timestamp_utc": "2025-08-10T10:40:35.582507+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,4 +30,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "0b40d364-b963-436a-88fd-5f9f2ba9de3b",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_medium"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_medium"],
|
||||
"recipe_name": "shape_medium",
|
||||
"timestamp_utc": "2025-08-10T09:43:15.290493+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 375.63
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "acc2002e-ac20-4e83-a772-e8e15a46abe7",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_small"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_small"],
|
||||
"recipe_name": "shape_small",
|
||||
"timestamp_utc": "2025-08-10T09:46:40.825505+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "597e376f-7ae5-4f08-acfd-3a5eeffb1a5c",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_small"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_small"],
|
||||
"recipe_name": "shape_small",
|
||||
"timestamp_utc": "2025-08-10T10:41:31.601581+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,4 +30,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@
|
||||
"memory_total_gb": 48
|
||||
},
|
||||
"id": "edb6828c-3605-4dde-9147-e97d2546a9b3",
|
||||
"location_paths": [
|
||||
"/Volumes/Seagate/benchdata/shape_small"
|
||||
],
|
||||
"location_paths": ["/Volumes/Seagate/benchdata/shape_small"],
|
||||
"recipe_name": "shape_small",
|
||||
"timestamp_utc": "2025-08-10T09:43:23.958382+00:00"
|
||||
},
|
||||
@@ -30,4 +28,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@
|
||||
"total_gb": 8.21
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@
|
||||
"hardware_model": null,
|
||||
"os": "macOS",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
channel = "stable"
|
||||
@@ -141,4 +141,4 @@ priority = 100
|
||||
|
||||
[file_types.metadata]
|
||||
compression = true
|
||||
single_file = true
|
||||
single_file = true
|
||||
@@ -290,4 +290,4 @@ priority = 100
|
||||
[[file_types.magic_bytes]]
|
||||
pattern = "4D 54 68 64"
|
||||
offset = 0
|
||||
priority = 100
|
||||
priority = 100
|
||||
@@ -135,7 +135,11 @@ header_file = true
|
||||
id = "text/javascript"
|
||||
name = "JavaScript"
|
||||
extensions = ["js", "mjs"]
|
||||
mime_types = ["text/javascript", "application/javascript", "application/x-javascript"]
|
||||
mime_types = [
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/x-javascript",
|
||||
]
|
||||
uti = "com.netscape.javascript-source"
|
||||
category = "code"
|
||||
priority = 80
|
||||
|
||||
@@ -90,7 +90,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
name = "Word Document (OOXML)"
|
||||
extensions = ["docx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
]
|
||||
uti = "org.openxmlformats.wordprocessingml.document"
|
||||
category = "document"
|
||||
priority = 100
|
||||
@@ -104,7 +106,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
name = "Excel Spreadsheet (OOXML)"
|
||||
extensions = ["xlsx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
]
|
||||
uti = "org.openxmlformats.spreadsheetml.sheet"
|
||||
category = "document"
|
||||
priority = 100
|
||||
@@ -118,7 +122,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
name = "PowerPoint Presentation (OOXML)"
|
||||
extensions = ["pptx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.presentationml.presentation"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
]
|
||||
uti = "org.openxmlformats.presentationml.presentation"
|
||||
category = "document"
|
||||
priority = 100
|
||||
@@ -219,7 +225,7 @@ category = "document"
|
||||
priority = 100
|
||||
|
||||
[[file_types.magic_bytes]]
|
||||
pattern = "53 44 4D 45 4D 01" # "SDMEM\x01"
|
||||
pattern = "53 44 4D 45 4D 01" # "SDMEM\x01"
|
||||
offset = 0
|
||||
priority = 100
|
||||
|
||||
|
||||
@@ -294,4 +294,4 @@ priority = 100
|
||||
|
||||
[file_types.metadata]
|
||||
raw_format = true
|
||||
manufacturer = "Panasonic"
|
||||
manufacturer = "Panasonic"
|
||||
@@ -4,7 +4,10 @@
|
||||
id = "application/x-debian-package"
|
||||
name = "Debian Package"
|
||||
extensions = ["deb"]
|
||||
mime_types = ["application/x-debian-package", "application/vnd.debian.binary-package"]
|
||||
mime_types = [
|
||||
"application/x-debian-package",
|
||||
"application/vnd.debian.binary-package",
|
||||
]
|
||||
category = "package"
|
||||
priority = 100
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ priority = 100
|
||||
id = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
name = "Microsoft PowerPoint (OpenXML)"
|
||||
extensions = ["pptx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.presentationml.presentation"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
]
|
||||
uti = "org.openxmlformats.presentationml.presentation"
|
||||
category = "presentation"
|
||||
priority = 100
|
||||
@@ -32,7 +34,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.presentationml.template"
|
||||
name = "PowerPoint Template (OpenXML)"
|
||||
extensions = ["potx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.presentationml.template"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
]
|
||||
category = "presentation"
|
||||
priority = 100
|
||||
|
||||
@@ -58,7 +62,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"
|
||||
name = "PowerPoint Slideshow"
|
||||
extensions = ["ppsx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.presentationml.slideshow"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
]
|
||||
category = "presentation"
|
||||
priority = 100
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ priority = 100
|
||||
id = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
name = "Microsoft Excel (OpenXML)"
|
||||
extensions = ["xlsx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
]
|
||||
uti = "org.openxmlformats.spreadsheetml.sheet"
|
||||
category = "spreadsheet"
|
||||
priority = 100
|
||||
@@ -32,7 +34,9 @@ priority = 90
|
||||
id = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"
|
||||
name = "Excel Template (OpenXML)"
|
||||
extensions = ["xltx"]
|
||||
mime_types = ["application/vnd.openxmlformats-officedocument.spreadsheetml.template"]
|
||||
mime_types = [
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
]
|
||||
category = "spreadsheet"
|
||||
priority = 100
|
||||
|
||||
|
||||
@@ -284,4 +284,4 @@ priority = 100
|
||||
[[file_types.magic_bytes]]
|
||||
pattern = "66 74 79 70"
|
||||
offset = 4
|
||||
priority = 90
|
||||
priority = 90
|
||||
@@ -134,7 +134,7 @@ This enables:
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "uuid-of-paired-device"
|
||||
"device_id": "uuid-of-paired-device"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -142,23 +142,23 @@ This enables:
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "uuid",
|
||||
"device_name": "Device Name",
|
||||
"libraries": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Library",
|
||||
"description": "Optional description",
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"statistics": {
|
||||
"total_entries": 1000,
|
||||
"total_locations": 5,
|
||||
"total_size_bytes": 1000000,
|
||||
"device_count": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_online": true
|
||||
"device_id": "uuid",
|
||||
"device_name": "Device Name",
|
||||
"libraries": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Library",
|
||||
"description": "Optional description",
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"statistics": {
|
||||
"total_entries": 1000,
|
||||
"total_locations": 5,
|
||||
"total_size_bytes": 1000000,
|
||||
"device_count": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_online": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -168,14 +168,14 @@ This enables:
|
||||
|
||||
```json
|
||||
{
|
||||
"local_device_id": "uuid",
|
||||
"remote_device_id": "uuid",
|
||||
"local_library_id": "uuid",
|
||||
"remote_library_id": "uuid",
|
||||
"action": {
|
||||
"type": "RegisterOnly"
|
||||
},
|
||||
"leader_device_id": "uuid"
|
||||
"local_device_id": "uuid",
|
||||
"remote_device_id": "uuid",
|
||||
"local_library_id": "uuid",
|
||||
"remote_library_id": "uuid",
|
||||
"action": {
|
||||
"type": "RegisterOnly"
|
||||
},
|
||||
"leader_device_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -183,11 +183,11 @@ This enables:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"local_library_id": "uuid",
|
||||
"remote_library_id": "uuid",
|
||||
"devices_registered": true,
|
||||
"message": "Devices successfully registered for library access"
|
||||
"success": true,
|
||||
"local_library_id": "uuid",
|
||||
"remote_library_id": "uuid",
|
||||
"devices_registered": true,
|
||||
"message": "Devices successfully registered for library access"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ Shared event collector for capturing and analyzing events from the event bus dur
|
||||
Collects all events emitted during test execution for analysis and verification.
|
||||
|
||||
**Basic Usage (Statistics Only):**
|
||||
|
||||
```rust
|
||||
use helpers::EventCollector;
|
||||
|
||||
@@ -34,6 +35,7 @@ stats.print();
|
||||
```
|
||||
|
||||
**Advanced Usage (Full Event Capture for Debugging):**
|
||||
|
||||
```rust
|
||||
// Create collector with full event data capture
|
||||
let mut collector = EventCollector::with_capture(&harness.core.events);
|
||||
@@ -58,6 +60,7 @@ let indexing_events = collector.get_events_by_type("IndexingCompleted").await;
|
||||
```
|
||||
|
||||
**Methods:**
|
||||
|
||||
- `new()` - Create collector for statistics only (lightweight)
|
||||
- `with_capture()` - Create collector that captures full event data
|
||||
- `collect_events(duration)` - Collect events for specified duration
|
||||
@@ -77,6 +80,7 @@ Statistics about collected events with formatted output:
|
||||
- **Entry** events (created/modified/deleted/moved)
|
||||
|
||||
**Use Cases:**
|
||||
|
||||
- Verifying watcher events during file operations
|
||||
- Testing normalized cache updates
|
||||
- Debugging event emission patterns
|
||||
@@ -99,6 +103,7 @@ let harness = IndexingHarnessBuilder::new("my_test")
|
||||
```
|
||||
|
||||
Automatically handles:
|
||||
|
||||
- Creating test directories
|
||||
- Initializing tracing
|
||||
- Setting up core and library
|
||||
@@ -133,12 +138,14 @@ handle.reindex().await?;
|
||||
#### Helper Classes
|
||||
|
||||
**`TestLocation`** - Builder for test locations:
|
||||
|
||||
- `write_file()` - Create files
|
||||
- `create_dir()` - Create directories
|
||||
- `create_filtered_files()` - Create files that should be filtered
|
||||
- `index()` - Index the location
|
||||
|
||||
**`LocationHandle`** - Handle to indexed location:
|
||||
|
||||
- `count_files()`, `count_directories()`, `count_entries()`
|
||||
- `get_all_entries()` - Get all indexed entries
|
||||
- `verify_no_filtered_entries()` - Assert filtering worked
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
[package]
|
||||
name = "sd-actors"
|
||||
name = "sd-actors"
|
||||
version = "0.1.0"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
pin-project-lite = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -1,26 +1,26 @@
|
||||
[package]
|
||||
name = "sd-archive"
|
||||
name = "sd-archive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# Core dependencies - workspace versions
|
||||
sqlx = { workspace = true, features = ["sqlite", "runtime-tokio", "derive"] }
|
||||
tokio = { workspace = true, features = ["process"] }
|
||||
serde = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
blake3 = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
sqlx = { workspace = true, features = ["sqlite", "runtime-tokio", "derive"] }
|
||||
tokio = { workspace = true, features = ["process"] }
|
||||
serde = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
blake3 = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
# Archive-specific dependencies - workspace versions
|
||||
indexmap = { workspace = true, features = ["serde"] }
|
||||
dashmap = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
|
||||
# Heavy dependencies for search/embedding - disabled for now to speed up integration
|
||||
# These will be enabled once core archive system is integrated
|
||||
|
||||
@@ -56,6 +56,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
### Integrated with Spacedrive
|
||||
|
||||
See `core/src/data/` for the Spacedrive integration wrapper that adds:
|
||||
|
||||
- Library-scoped lifecycle
|
||||
- Job system integration
|
||||
- Event bus integration
|
||||
@@ -129,6 +130,7 @@ fields = [
|
||||
```
|
||||
|
||||
**Field types:**
|
||||
|
||||
- `String` - Short text (up to 1KB)
|
||||
- `Text` - Long text (unlimited)
|
||||
- `Integer` - i64
|
||||
@@ -139,6 +141,7 @@ fields = [
|
||||
- `ForeignKey` - Reference to another type
|
||||
|
||||
**Field flags:**
|
||||
|
||||
- `indexed: true` - Create FTS5 index for full-text search
|
||||
- `embedded: true` - Generate vector embeddings for semantic search
|
||||
- `unique: true` - Enforce uniqueness constraint
|
||||
@@ -153,7 +156,7 @@ Adapters communicate via stdin/stdout using line-delimited JSON.
|
||||
Config object sent once at startup:
|
||||
|
||||
```json
|
||||
{"email": "user@example.com", "cursor": "abc123"}
|
||||
{ "email": "user@example.com", "cursor": "abc123" }
|
||||
```
|
||||
|
||||
### Output (stdout)
|
||||
@@ -167,6 +170,7 @@ Stream of operation objects:
|
||||
```
|
||||
|
||||
**Operations:**
|
||||
|
||||
- `upsert` - Insert or update record
|
||||
- `delete` - Delete record
|
||||
- `link` - Create relationship between records
|
||||
@@ -176,7 +180,7 @@ Stream of operation objects:
|
||||
Adapters maintain cursor state for incremental sync:
|
||||
|
||||
```json
|
||||
{"op": "cursor", "value": "next-page-token-xyz"}
|
||||
{ "op": "cursor", "value": "next-page-token-xyz" }
|
||||
```
|
||||
|
||||
The engine persists cursor state and provides it on next sync.
|
||||
@@ -184,6 +188,7 @@ The engine persists cursor state and provides it on next sync.
|
||||
## Dependencies
|
||||
|
||||
**Core:**
|
||||
|
||||
- `sqlx` - SQLite database operations
|
||||
- `toml` - Schema parsing
|
||||
- `serde` / `serde_json` - Serialization
|
||||
@@ -192,10 +197,12 @@ The engine persists cursor state and provides it on next sync.
|
||||
- `blake3` - Content hashing
|
||||
|
||||
**Search:**
|
||||
|
||||
- `lancedb` - Vector database
|
||||
- `fastembed` - Embedding model
|
||||
|
||||
**Safety (optional):**
|
||||
|
||||
- `ort` - ONNX Runtime for Prompt Guard 2
|
||||
- `tokenizers` - Text tokenization
|
||||
- `hf-hub` - Model downloads
|
||||
@@ -213,6 +220,7 @@ The engine persists cursor state and provides it on next sync.
|
||||
- Embedding generation: ~100 records/sec (CPU bound)
|
||||
|
||||
**Memory:**
|
||||
|
||||
- Engine overhead: ~10MB
|
||||
- Per-source overhead: ~5MB
|
||||
- LanceDB cache: ~50MB
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
[package]
|
||||
name = "sd-crypto"
|
||||
name = "sd-crypto"
|
||||
version = "0.0.1"
|
||||
|
||||
authors = [
|
||||
"Arnab Chakraborty <arnab@spacedrive.com>",
|
||||
"Ericson Soares <ericson@spacedrive.com>",
|
||||
"Jake Robinson <jake@spacedrive.com>"
|
||||
"Arnab Chakraborty <arnab@spacedrive.com>",
|
||||
"Ericson Soares <ericson@spacedrive.com>",
|
||||
"Jake Robinson <jake@spacedrive.com>",
|
||||
]
|
||||
description = """
|
||||
A cryptographic library that provides safe and high-level
|
||||
encryption, hashing, and encoding interfaces.
|
||||
"""
|
||||
|
||||
edition.workspace = true
|
||||
keywords = ["crypto"]
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
keywords = ["crypto"]
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies
|
||||
async-stream = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
blake3 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "rt-multi-thread", "sync"] }
|
||||
tracing = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
blake3 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"io-util",
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"sync",
|
||||
] }
|
||||
tracing = { workspace = true }
|
||||
tracing-test = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["derive"] }
|
||||
zeroize = { workspace = true, features = ["derive"] }
|
||||
|
||||
# External dependencies
|
||||
aead = { version = "0.6.0-rc.2", default-features = false }
|
||||
aead = { version = "0.6.0-rc.2", default-features = false }
|
||||
chacha20poly1305 = "0.11.0-rc.1"
|
||||
cmov = "0.4.4"
|
||||
generic-array = { version = "=0.14.7", features = ["serde", "zeroize"] } # Update blocked by aead
|
||||
hex = "0.4.3"
|
||||
rand_chacha = "0.9.0-alpha.2"
|
||||
rand_core = "0.9.0-alpha.2"
|
||||
serdect = "0.3.0-pre.0"
|
||||
typenum = "1.17"
|
||||
cmov = "0.4.4"
|
||||
generic-array = { version = "=0.14.7", features = ["serde", "zeroize"] } # Update blocked by aead
|
||||
hex = "0.4.3"
|
||||
rand_chacha = "0.9.0-alpha.2"
|
||||
rand_core = "0.9.0-alpha.2"
|
||||
serdect = "0.3.0-pre.0"
|
||||
typenum = "1.17"
|
||||
|
||||
old-rand-core = { package = "rand_core", version = "0.6.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
paste = "1.0"
|
||||
paste = "1.0"
|
||||
tempfile = "3.10"
|
||||
|
||||
[[example]]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "sd-fda"
|
||||
name = "sd-fda"
|
||||
version = "0.1.0"
|
||||
|
||||
authors = ["Jake Robinson <jake@spacedrive.com>"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors = ["Jake Robinson <jake@spacedrive.com>"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
[package]
|
||||
name = "sd-ffmpeg"
|
||||
name = "sd-ffmpeg"
|
||||
version = "0.1.0"
|
||||
|
||||
authors = ["Ericson Soares <ericson@spacedrive.com>", "Vítor Vasconcellos <vitor@spacedrive.com>"]
|
||||
description = "A simple library to generate video thumbnails using ffmpeg with the webp format"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
authors = [
|
||||
"Ericson Soares <ericson@spacedrive.com>",
|
||||
"Vítor Vasconcellos <vitor@spacedrive.com>",
|
||||
]
|
||||
description = "A simple library to generate video thumbnails using ffmpeg with the webp format"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -15,17 +18,17 @@ rust-version.workspace = true
|
||||
sd-utils = { path = "../utils" }
|
||||
|
||||
# Workspace dependencies
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
image = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
image = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt", "io-util"] }
|
||||
tracing = { workspace = true }
|
||||
webp = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt", "io-util"] }
|
||||
tracing = { workspace = true }
|
||||
webp = { workspace = true }
|
||||
|
||||
# Specific FFmpeg dependencies
|
||||
ffmpeg-sys-next = "7.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "macros", "rt"] }
|
||||
tokio = { workspace = true, features = ["fs", "macros", "rt"] }
|
||||
@@ -201,5 +201,3 @@ tokio::spawn(async move {
|
||||
### Database-Backed Inode Lookup
|
||||
|
||||
For enhanced rename detection on macOS, the `PersistentIndexService` can maintain an inode cache. When a Remove event is received, check if the inode exists in your database to detect if it's actually a rename where the "new path" hasn't arrived yet.
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
[package]
|
||||
name = "sd-images"
|
||||
name = "sd-images"
|
||||
version = "0.1.0"
|
||||
|
||||
authors = ["Jake Robinson <jake@spacedrive.com>", "Vítor Vasconcellos <vitor@spacedrive.com>"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors = [
|
||||
"Jake Robinson <jake@spacedrive.com>",
|
||||
"Vítor Vasconcellos <vitor@spacedrive.com>",
|
||||
]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
@@ -12,23 +15,26 @@ heif = ["dep:libheif-rs", "dep:libheif-sys"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
specta = { workspace = true, optional = true }
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
specta = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Specific Images dependencies
|
||||
bincode = { version = "=2.0.0-rc.3", features = ["alloc", "derive"], optional = true }
|
||||
# Disable defaults for libheif* to avoid bindgen and use pre-compiled headers
|
||||
libheif-rs = { version = "1.0", default-features = false, optional = true }
|
||||
libheif-rs = { version = "1.0", default-features = false, optional = true }
|
||||
libheif-sys = { version = "2.1", default-features = false, optional = true }
|
||||
# Keep the same resvg backend and raster semantics locally. The OS layer does
|
||||
# not depend on the UI workspace; see svg_raster.rs for the adaptation.
|
||||
resvg = { version = "0.47", default-features = false, features = ["system-fonts", "text"] }
|
||||
resvg = { version = "0.47", default-features = false, features = [
|
||||
"system-fonts",
|
||||
"text",
|
||||
] }
|
||||
|
||||
[dependencies.pdfium-render]
|
||||
default-features = false
|
||||
features = ["image", "pdfium_6666", "sync", "thread_safe"]
|
||||
git = "https://github.com/spacedriveapp/pdfium-render.git"
|
||||
rev = "983f8d8"
|
||||
features = ["image", "pdfium_6666", "sync", "thread_safe"]
|
||||
git = "https://github.com/spacedriveapp/pdfium-render.git"
|
||||
rev = "983f8d8"
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "job-derive"
|
||||
name = "job-derive"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
@@ -8,5 +8,5 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
@@ -38,7 +38,3 @@ name = "analyze_sync_log"
|
||||
required-features = ["cli"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "sd-media-metadata"
|
||||
name = "sd-media-metadata"
|
||||
version = "0.1.0"
|
||||
|
||||
authors = [
|
||||
"Ericson Soares <ericson@spacedrive.com>",
|
||||
"Jake Robinson <jake@spacedrive.com>",
|
||||
"Vítor Vasconcellos <vitor@spacedrive.com>"
|
||||
"Ericson Soares <ericson@spacedrive.com>",
|
||||
"Jake Robinson <jake@spacedrive.com>",
|
||||
"Vítor Vasconcellos <vitor@spacedrive.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
|
||||
@@ -15,16 +15,16 @@ ffmpeg = ["dep:sd-ffmpeg"]
|
||||
[dependencies]
|
||||
# Spacedrive Sub-crates
|
||||
sd-ffmpeg = { path = "../ffmpeg", optional = true }
|
||||
sd-utils = { path = "../utils" }
|
||||
sd-utils = { path = "../utils" }
|
||||
|
||||
# Workspace dependencies
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
specta = { workspace = true, features = ["chrono", "derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
specta = { workspace = true, features = ["chrono", "derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
|
||||
# Specific Media Metadata dependencies
|
||||
kamadak-exif = "0.5.5"
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
description = "Proc macros for Spacedrive extension SDK"
|
||||
edition = "2021"
|
||||
name = "spacedrive-sdk-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
name = "spacedrive-sdk-macros"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["extra-traits", "full"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["extra-traits", "full"] }
|
||||
@@ -10,6 +10,7 @@
|
||||
### `#[model]` Macro - Enhanced!
|
||||
|
||||
**Handles all field attributes:**
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[model(version = "1.0.0")]
|
||||
@@ -47,6 +48,7 @@ struct Photo {
|
||||
```
|
||||
|
||||
**What the macro does:**
|
||||
|
||||
1. Parses and strips field attributes
|
||||
2. Generates `ExtensionModel` trait impl
|
||||
3. Finds `id` or `uuid` field automatically
|
||||
@@ -54,6 +56,7 @@ struct Photo {
|
||||
5. Compiles successfully
|
||||
|
||||
**Recognized attributes:**
|
||||
|
||||
- `#[entry]` - References file/directory
|
||||
- `#[sidecar]` - Extension-owned derivative data
|
||||
- `#[metadata]` - Core-extracted metadata
|
||||
@@ -68,16 +71,16 @@ struct Photo {
|
||||
|
||||
## Other Macros
|
||||
|
||||
| Macro | Status | Notes |
|
||||
|-------|--------|-------|
|
||||
| `#[extension]` | Working | Generates plugin_init(), job registration |
|
||||
| `#[job]` | Working | FFI exports (proven in test-extension) |
|
||||
| `#[model]` | Enhanced | Handles field attributes! |
|
||||
| `#[agent]` | Pass-through | Needs impl block handling |
|
||||
| `#[agent_memory]` | Working | Generates AgentMemory trait |
|
||||
| `#[task]` | Pass-through | Needs implementation |
|
||||
| `#[action]` | Pass-through | Needs implementation |
|
||||
| `#[query]` | Pass-through | Needs implementation |
|
||||
| Macro | Status | Notes |
|
||||
| ----------------- | ------------ | ----------------------------------------- |
|
||||
| `#[extension]` | Working | Generates plugin_init(), job registration |
|
||||
| `#[job]` | Working | FFI exports (proven in test-extension) |
|
||||
| `#[model]` | Enhanced | Handles field attributes! |
|
||||
| `#[agent]` | Pass-through | Needs impl block handling |
|
||||
| `#[agent_memory]` | Working | Generates AgentMemory trait |
|
||||
| `#[task]` | Pass-through | Needs implementation |
|
||||
| `#[action]` | Pass-through | Needs implementation |
|
||||
| `#[query]` | Pass-through | Needs implementation |
|
||||
|
||||
---
|
||||
|
||||
@@ -106,6 +109,7 @@ struct PhotoAnalysis {
|
||||
```
|
||||
|
||||
**The attributes:**
|
||||
|
||||
- Show design intent
|
||||
- Will be processed when macro is enhanced
|
||||
- Don't break compilation
|
||||
@@ -118,6 +122,7 @@ struct PhotoAnalysis {
|
||||
### For Full Compilation
|
||||
|
||||
Photos extension needs:
|
||||
|
||||
1. Convert async jobs to sync (match test-extension pattern)
|
||||
2. OR: Enhance `#[job]` macro to support async
|
||||
3. OR: Keep as aspirational reference
|
||||
@@ -141,5 +146,4 @@ Photos extension needs:
|
||||
|
||||
---
|
||||
|
||||
**The macro system now supports the design! Field attributes work.**
|
||||
|
||||
**The macro system now supports the design! Field attributes work.**
|
||||
@@ -398,18 +398,18 @@ fn test_counter(ctx: &JobContext, state: &mut CounterState) -> Result<()> {
|
||||
|
||||
## Implementation Status
|
||||
|
||||
| Module | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| `ffi.rs` | Implemented | Low-level WASM imports |
|
||||
| `job_context.rs` | Expanded | New methods added, existing preserved |
|
||||
| `types.rs` | Expanded | All common types added |
|
||||
| `vdfs.rs` | Stubs | Type-checks, `todo!()` for host calls |
|
||||
| `ai.rs` | Stubs | Type-checks, `todo!()` for inference |
|
||||
| `agent.rs` | Stubs | Type-checks, memory system defined |
|
||||
| `models.rs` | Stubs | Type-checks, registration stubs |
|
||||
| `actions.rs` | Stubs | Type-checks, preview/execute defined |
|
||||
| `tasks.rs` | Stubs | Type-checks, task context defined |
|
||||
| `query.rs` | Stubs | Type-checks, query context defined |
|
||||
| Module | Status | Notes |
|
||||
| ---------------- | ----------- | ------------------------------------- |
|
||||
| `ffi.rs` | Implemented | Low-level WASM imports |
|
||||
| `job_context.rs` | Expanded | New methods added, existing preserved |
|
||||
| `types.rs` | Expanded | All common types added |
|
||||
| `vdfs.rs` | Stubs | Type-checks, `todo!()` for host calls |
|
||||
| `ai.rs` | Stubs | Type-checks, `todo!()` for inference |
|
||||
| `agent.rs` | Stubs | Type-checks, memory system defined |
|
||||
| `models.rs` | Stubs | Type-checks, registration stubs |
|
||||
| `actions.rs` | Stubs | Type-checks, preview/execute defined |
|
||||
| `tasks.rs` | Stubs | Type-checks, task context defined |
|
||||
| `query.rs` | Stubs | Type-checks, query context defined |
|
||||
|
||||
**Macros:** All defined as pass-through (no codegen yet)
|
||||
|
||||
@@ -436,6 +436,7 @@ fn test_counter(ctx: &JobContext, state: &mut CounterState) -> Result<()> {
|
||||
### Phase 1: Core Host Functions
|
||||
|
||||
Implement in `core/src/infra/extension/host_functions.rs`:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern "C" fn vdfs_query_entries(...) -> u32;
|
||||
@@ -454,6 +455,7 @@ pub extern "C" fn ai_infer(...) -> u32;
|
||||
### Phase 2: SDK Implementation
|
||||
|
||||
Replace `todo!()` with actual WASM host calls:
|
||||
|
||||
```rust
|
||||
pub async fn get_model_by_content<T>(content_uuid: Uuid) -> Result<T> {
|
||||
// Serialize request
|
||||
@@ -476,6 +478,7 @@ pub async fn get_model_by_content<T>(content_uuid: Uuid) -> Result<T> {
|
||||
### Phase 3: Macro Code Generation
|
||||
|
||||
Implement real macros:
|
||||
|
||||
```rust
|
||||
#[model(scope = "content")]
|
||||
struct PhotoAnalysis { ... }
|
||||
@@ -526,16 +529,16 @@ cargo build --target wasm32-unknown-unknown --release
|
||||
## Breaking Changes
|
||||
|
||||
**None!** The existing test-extension API is preserved:
|
||||
- `ctx.log()`
|
||||
- `ctx.checkpoint()`
|
||||
- `ctx.check_interrupt()`
|
||||
- `ctx.report_progress()`
|
||||
- `ctx.increment_items()`
|
||||
- `ctx.increment_bytes()`
|
||||
|
||||
- `ctx.log()`
|
||||
- `ctx.checkpoint()`
|
||||
- `ctx.check_interrupt()`
|
||||
- `ctx.report_progress()`
|
||||
- `ctx.increment_items()`
|
||||
- `ctx.increment_bytes()`
|
||||
|
||||
New methods are additive only.
|
||||
|
||||
---
|
||||
|
||||
**The SDK is now complete for type-checking. Extensions can be written and will compile. Runtime implementation is the next phase.**
|
||||
|
||||
**The SDK is now complete for type-checking. Extensions can be written and will compile. Runtime implementation is the next phase.**
|
||||
@@ -1,19 +1,22 @@
|
||||
[package]
|
||||
authors = ["Spacedrive Technology Inc."]
|
||||
authors = ["Spacedrive Technology Inc."]
|
||||
description = "SDK for building Spacedrive WASM extensions"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "spacedrive-sdk"
|
||||
repository = "https://github.com/spacedriveapp/spacedrive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "spacedrive-sdk"
|
||||
repository = "https://github.com/spacedriveapp/spacedrive"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", default-features = false, features = ["serde"] }
|
||||
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
chrono = { version = "0.4", default-features = false, features = ["serde"] }
|
||||
serde = { version = "1.0", default-features = false, features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
spacedrive-sdk-macros = { path = "../sdk-macros" }
|
||||
thiserror = { version = "1.0", default-features = false }
|
||||
uuid = { version = "1.0", default-features = false, features = ["serde", "v4"] }
|
||||
thiserror = { version = "1.0", default-features = false }
|
||||
uuid = { version = "1.0", default-features = false, features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", features = ["macros", "rt"] }
|
||||
@@ -55,6 +55,7 @@ fn process_receipt(ctx: &ExtensionContext, pdf_data: &[u8]) -> Result<Uuid> {
|
||||
## No Unsafe, No FFI, Just Clean Rust
|
||||
|
||||
**Before (raw C bindings):**
|
||||
|
||||
```rust
|
||||
#[link(wasm_import_module = "spacedrive")]
|
||||
extern "C" {
|
||||
@@ -77,6 +78,7 @@ extern "C" {
|
||||
```
|
||||
|
||||
**After (with SDK):**
|
||||
|
||||
```rust
|
||||
let entry = ctx.vdfs().create_entry(CreateEntry { ... })?;
|
||||
let ocr = ctx.ai().ocr(&pdf_data, OcrOptions::default())?;
|
||||
@@ -252,4 +254,3 @@ See `extensions/test-extension/` for a complete working example.
|
||||
---
|
||||
|
||||
**Beautiful API. Zero unsafe code. Just Rust. **
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
[package]
|
||||
name = "sd-task-system"
|
||||
name = "sd-task-system"
|
||||
version = "0.1.0"
|
||||
|
||||
authors = ["Ericson Soares <ericson@spacedrive.com>"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors = ["Ericson Soares <ericson@spacedrive.com>"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Workspace deps
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-concurrency = { workspace = true }
|
||||
pin-project-lite = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["parking_lot", "rt-multi-thread", "sync", "time"] }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
pin-project-lite = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"parking_lot",
|
||||
"rt-multi-thread",
|
||||
"sync",
|
||||
"time",
|
||||
] }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
|
||||
# Specific Task System dependencies
|
||||
downcast-rs = "1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
lending-stream = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "macros", "test-util"] }
|
||||
lending-stream = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "macros", "test-util"] }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
tracing-test = { workspace = true, features = ["no-env-filter"] }
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
tracing-test = { workspace = true, features = ["no-env-filter"] }
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
@@ -1,15 +1,15 @@
|
||||
[package]
|
||||
authors = ["Jamie Pine <jamie@spacedrive.com>"]
|
||||
edition = "2021"
|
||||
name = "task-validator"
|
||||
name = "task-validator"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
comfy-table = "7.1"
|
||||
glob = "0.3"
|
||||
jsonschema = "0.17"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
glob = "0.3"
|
||||
jsonschema = "0.17"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
@@ -8,4 +8,4 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = []
|
||||
@@ -1,5 +1,5 @@
|
||||
# Local crate patches (Aphrody Drive)
|
||||
|
||||
| Crate | Why |
|
||||
|---|---|
|
||||
| Crate | Why |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ffmpeg-sys-next` 7.1.3 | Its build script uses bindgen 0.70, which generates empty structs (`_address: u8`) against libclang 21+ while still emitting the real layout assertions, so the bindings fail to compile. This copy only raises the build-dependency to bindgen 0.72. Drop it once `ffmpeg-sys-next` ships a bindgen 0.72+ release for FFmpeg 7.1. |
|
||||
@@ -12,10 +12,7 @@
|
||||
[package]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "7.1.3"
|
||||
authors = [
|
||||
"meh. <meh@schizofreni.co>",
|
||||
"Zhiming Wang <i@zhimingwang.org>",
|
||||
]
|
||||
authors = ["meh. <meh@schizofreni.co>", "Zhiming Wang <i@zhimingwang.org>"]
|
||||
build = "build.rs"
|
||||
links = "ffmpeg"
|
||||
autolib = false
|
||||
@@ -25,10 +22,7 @@ autotests = false
|
||||
autobenches = false
|
||||
description = "FFI bindings to FFmpeg"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"audio",
|
||||
"video",
|
||||
]
|
||||
keywords = ["audio", "video"]
|
||||
license = "WTFPL"
|
||||
repository = "https://github.com/zmwangx/rust-ffmpeg-sys"
|
||||
|
||||
@@ -102,12 +96,12 @@ build-videotoolbox = ["build"]
|
||||
build-vulkan = ["build"]
|
||||
build-zlib = ["build"]
|
||||
default = [
|
||||
"avcodec",
|
||||
"avdevice",
|
||||
"avfilter",
|
||||
"avformat",
|
||||
"swresample",
|
||||
"swscale",
|
||||
"avcodec",
|
||||
"avdevice",
|
||||
"avfilter",
|
||||
"avformat",
|
||||
"swresample",
|
||||
"swscale",
|
||||
]
|
||||
non-exhaustive-enums = []
|
||||
postproc = []
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "xtask"
|
||||
name = "xtask"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
flate2 = "1.0"
|
||||
mustache = "0.9"
|
||||
anyhow = "1"
|
||||
flate2 = "1.0"
|
||||
mustache = "0.9"
|
||||
owo-colors = "4"
|
||||
reqwest = { version = "0.12", features = ["blocking", "json", "rustls-tls"], default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.12", features = ["blocking", "json", "rustls-tls"], default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tar = "0.4"
|
||||
toml = "0.8"
|
||||
which = "6.0"
|
||||
xz2 = "0.1"
|
||||
tar = "0.4"
|
||||
toml = "0.8"
|
||||
which = "6.0"
|
||||
xz2 = "0.1"
|
||||
@@ -5,18 +5,72 @@ export const AGE1000_MAP = {
|
||||
width: 1200,
|
||||
height: 760,
|
||||
areas: [
|
||||
{ id: "university", label: "Université", x: 70, y: 120, w: 245, h: 210, color: "#315568" },
|
||||
{ id: "city", label: "Centre urbain", x: 280, y: 160, w: 600, h: 430, color: "#284258" },
|
||||
{ id: "capsule", label: "Quartier Capsule", x: 830, y: 105, w: 300, h: 230, color: "#35536c" },
|
||||
{ id: "plaza", label: "Esplanade", x: 870, y: 490, w: 260, h: 180, color: "#274b5c" },
|
||||
{ id: "glacier", label: "Porte glaciaire", x: 70, y: 505, w: 245, h: 165, color: "#536a79" },
|
||||
{
|
||||
id: "university",
|
||||
label: "Université",
|
||||
x: 70,
|
||||
y: 120,
|
||||
w: 245,
|
||||
h: 210,
|
||||
color: "#315568",
|
||||
},
|
||||
{
|
||||
id: "city",
|
||||
label: "Centre urbain",
|
||||
x: 280,
|
||||
y: 160,
|
||||
w: 600,
|
||||
h: 430,
|
||||
color: "#284258",
|
||||
},
|
||||
{
|
||||
id: "capsule",
|
||||
label: "Quartier Capsule",
|
||||
x: 830,
|
||||
y: 105,
|
||||
w: 300,
|
||||
h: 230,
|
||||
color: "#35536c",
|
||||
},
|
||||
{
|
||||
id: "plaza",
|
||||
label: "Esplanade",
|
||||
x: 870,
|
||||
y: 490,
|
||||
w: 260,
|
||||
h: 180,
|
||||
color: "#274b5c",
|
||||
},
|
||||
{
|
||||
id: "glacier",
|
||||
label: "Porte glaciaire",
|
||||
x: 70,
|
||||
y: 505,
|
||||
w: 245,
|
||||
h: 165,
|
||||
color: "#536a79",
|
||||
},
|
||||
],
|
||||
landmarks: [
|
||||
{ id: "university", label: "West City University", x: 115, y: 195, w: 150, h: 86 },
|
||||
{
|
||||
id: "university",
|
||||
label: "West City University",
|
||||
x: 115,
|
||||
y: 195,
|
||||
w: 150,
|
||||
h: 86,
|
||||
},
|
||||
{ id: "squad", label: "Great Saiya Squad", x: 365, y: 190, w: 165, h: 76 },
|
||||
{ id: "plaza", label: "Central Plaza", x: 565, y: 320, w: 155, h: 100 },
|
||||
{ id: "capsule", label: "Capsule Complex", x: 900, y: 175, w: 150, h: 90 },
|
||||
{ id: "monorail", label: "Monorail Station", x: 675, y: 525, w: 135, h: 55 },
|
||||
{
|
||||
id: "monorail",
|
||||
label: "Monorail Station",
|
||||
x: 675,
|
||||
y: 525,
|
||||
w: 135,
|
||||
h: 55,
|
||||
},
|
||||
{ id: "glacier", label: "Glacier Outpost", x: 115, y: 545, w: 150, h: 75 },
|
||||
],
|
||||
};
|
||||
@@ -82,7 +136,12 @@ export function drawAge1000Map(ctx, width, height) {
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "600 15px system-ui";
|
||||
ctx.fillText(place.label, place.x + 12, place.y + place.h / 2 + 5, place.w - 24);
|
||||
ctx.fillText(
|
||||
place.label,
|
||||
place.x + 12,
|
||||
place.y + place.h / 2 + 5,
|
||||
place.w - 24,
|
||||
);
|
||||
}
|
||||
ctx.fillStyle = "#e1eff8";
|
||||
ctx.font = "800 24px system-ui";
|
||||
|
||||
@@ -10,7 +10,14 @@ export const DEFAULT_AVATAR = {
|
||||
height: 1,
|
||||
};
|
||||
|
||||
const races = new Set(["saiyan", "earthling", "namekian", "majin", "frieza", "android"]);
|
||||
const races = new Set([
|
||||
"saiyan",
|
||||
"earthling",
|
||||
"namekian",
|
||||
"majin",
|
||||
"frieza",
|
||||
"android",
|
||||
]);
|
||||
const hairstyles = new Set(["spiky", "short", "crest"]);
|
||||
const builds = new Set(["slim", "athletic", "broad"]);
|
||||
const colorPattern = /^#[0-9a-f]{6}$/i;
|
||||
@@ -25,8 +32,12 @@ export function normalizeAvatar(value) {
|
||||
race: races.has(avatar.race) ? avatar.race : DEFAULT_AVATAR.race,
|
||||
skin: colorPattern.test(avatar.skin) ? avatar.skin : DEFAULT_AVATAR.skin,
|
||||
hair: colorPattern.test(avatar.hair) ? avatar.hair : DEFAULT_AVATAR.hair,
|
||||
outfit: colorPattern.test(avatar.outfit) ? avatar.outfit : DEFAULT_AVATAR.outfit,
|
||||
hairstyle: hairstyles.has(avatar.hairstyle) ? avatar.hairstyle : DEFAULT_AVATAR.hairstyle,
|
||||
outfit: colorPattern.test(avatar.outfit)
|
||||
? avatar.outfit
|
||||
: DEFAULT_AVATAR.outfit,
|
||||
hairstyle: hairstyles.has(avatar.hairstyle)
|
||||
? avatar.hairstyle
|
||||
: DEFAULT_AVATAR.hairstyle,
|
||||
build: builds.has(avatar.build) ? avatar.build : DEFAULT_AVATAR.build,
|
||||
height: Number.isFinite(Number(avatar.height))
|
||||
? Math.max(0.85, Math.min(1.15, Number(avatar.height)))
|
||||
@@ -36,7 +47,9 @@ export function normalizeAvatar(value) {
|
||||
|
||||
export function loadAvatar(storage) {
|
||||
try {
|
||||
return normalizeAvatar(JSON.parse(storage.getItem("dragon-pixel-avatar") || "null"));
|
||||
return normalizeAvatar(
|
||||
JSON.parse(storage.getItem("dragon-pixel-avatar") || "null"),
|
||||
);
|
||||
} catch {
|
||||
return { ...DEFAULT_AVATAR };
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ export function step(g, input, dt) {
|
||||
p.transformed = Math.max(0, p.transformed - dt);
|
||||
const move = (input.right ? 1 : 0) - (input.left ? 1 : 0);
|
||||
if (move) p.facing = move;
|
||||
p.x = Math.max(24, Math.min(936, p.x + move * (p.style === "speed" ? 280 : 215) * dt));
|
||||
p.x = Math.max(
|
||||
24,
|
||||
Math.min(936, p.x + move * (p.style === "speed" ? 280 : 215) * dt),
|
||||
);
|
||||
if (input.jump && p.y >= 320) p.vy = -420;
|
||||
p.vy += 980 * dt;
|
||||
p.y = Math.min(320, p.y + p.vy * dt);
|
||||
@@ -46,14 +49,23 @@ export function step(g, input, dt) {
|
||||
p.combo = (p.combo % 3) + 1;
|
||||
p.cooldown = 0.28;
|
||||
for (const e of g.enemies)
|
||||
if (Math.abs(e.x - p.x) < 85 && Math.abs(e.y - p.y) < 65 && (e.x - p.x) * p.facing >= 0) {
|
||||
if (
|
||||
Math.abs(e.x - p.x) < 85 &&
|
||||
Math.abs(e.y - p.y) < 65 &&
|
||||
(e.x - p.x) * p.facing >= 0
|
||||
) {
|
||||
e.hp -= (10 + p.combo * 3) * power;
|
||||
e.x += p.facing * 18;
|
||||
}
|
||||
} else if (input.blast && !p.cooldown && p.energy >= 20) {
|
||||
p.energy -= 20;
|
||||
p.cooldown = 0.45;
|
||||
g.shots.push({ x: p.x + p.facing * 24, y: p.y - 20, vx: p.facing * 520, damage: 24 * power });
|
||||
g.shots.push({
|
||||
x: p.x + p.facing * 24,
|
||||
y: p.y - 20,
|
||||
vx: p.facing * 520,
|
||||
damage: 24 * power,
|
||||
});
|
||||
}
|
||||
if (!g.enemies.length && g.spawn <= 0) {
|
||||
if (g.wave === 5) {
|
||||
@@ -62,13 +74,22 @@ export function step(g, input, dt) {
|
||||
}
|
||||
g.wave++;
|
||||
for (let i = 0; i < g.wave + 1; i++)
|
||||
g.enemies.push({ x: 660 + i * 45, y: 320, hp: 35 + g.wave * 8, cooldown: 0.5 + i * 0.2 });
|
||||
g.enemies.push({
|
||||
x: 660 + i * 45,
|
||||
y: 320,
|
||||
hp: 35 + g.wave * 8,
|
||||
cooldown: 0.5 + i * 0.2,
|
||||
});
|
||||
}
|
||||
g.spawn = Math.max(0, g.spawn - dt);
|
||||
for (const s of g.shots) {
|
||||
s.x += s.vx * dt;
|
||||
for (const e of g.enemies)
|
||||
if (e.hp > 0 && Math.abs(e.x - s.x) < 28 && Math.abs(e.y - 20 - s.y) < 38) {
|
||||
if (
|
||||
e.hp > 0 &&
|
||||
Math.abs(e.x - s.x) < 28 &&
|
||||
Math.abs(e.y - 20 - s.y) < 38
|
||||
) {
|
||||
e.hp -= s.damage;
|
||||
s.x = -1000;
|
||||
break;
|
||||
@@ -83,8 +104,13 @@ export function step(g, input, dt) {
|
||||
}
|
||||
e.cooldown -= dt;
|
||||
const distance = p.x - e.x;
|
||||
if (Math.abs(distance) > 42) e.x += Math.sign(distance) * (65 + g.wave * 8) * dt;
|
||||
if (Math.abs(distance) < 55 && Math.abs(p.y - e.y) < 55 && e.cooldown <= 0) {
|
||||
if (Math.abs(distance) > 42)
|
||||
e.x += Math.sign(distance) * (65 + g.wave * 8) * dt;
|
||||
if (
|
||||
Math.abs(distance) < 55 &&
|
||||
Math.abs(p.y - e.y) < 55 &&
|
||||
e.cooldown <= 0
|
||||
) {
|
||||
p.hp = Math.max(0, p.hp - (p.transformed ? 3 : 6));
|
||||
e.cooldown = 0.85;
|
||||
}
|
||||
@@ -96,7 +122,12 @@ export function step(g, input, dt) {
|
||||
}
|
||||
|
||||
export function pointerDirection(targetX, playerX, tolerance = 12) {
|
||||
if (targetX === null || !Number.isFinite(targetX) || !Number.isFinite(playerX)) return null;
|
||||
if (
|
||||
targetX === null ||
|
||||
!Number.isFinite(targetX) ||
|
||||
!Number.isFinite(playerX)
|
||||
)
|
||||
return null;
|
||||
const difference = targetX - playerX;
|
||||
if (Math.abs(difference) < tolerance) return null;
|
||||
return difference < 0 ? "left" : "right";
|
||||
|
||||
@@ -2,8 +2,16 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { createGame, pointerAction, pointerDirection, step } from "./engine.js";
|
||||
import { readGamepadInput } from "./gamepad.js";
|
||||
import { DEFAULT_AVATAR, loadAvatar, normalizeAvatar, saveAvatar } from "./avatar.js";
|
||||
import { buildLoreSearchRequest, filterLoreSearchPayload } from "./lore-search.js";
|
||||
import {
|
||||
DEFAULT_AVATAR,
|
||||
loadAvatar,
|
||||
normalizeAvatar,
|
||||
saveAvatar,
|
||||
} from "./avatar.js";
|
||||
import {
|
||||
buildLoreSearchRequest,
|
||||
filterLoreSearchPayload,
|
||||
} from "./lore-search.js";
|
||||
import {
|
||||
buildLoreCatalogRequest,
|
||||
normalizeLoreCatalogPayload,
|
||||
@@ -121,7 +129,9 @@ test("avatar creation validates and persists local character choices", () => {
|
||||
};
|
||||
expect(saveAvatar(storage, values)).toEqual({ ...values, name: "Nova" });
|
||||
expect(loadAvatar(storage)).toEqual({ ...values, name: "Nova" });
|
||||
expect(normalizeAvatar({ ...values, name: "", race: "alien", skin: "red" })).toEqual({
|
||||
expect(
|
||||
normalizeAvatar({ ...values, name: "", race: "alien", skin: "red" }),
|
||||
).toEqual({
|
||||
...DEFAULT_AVATAR,
|
||||
hair: values.hair,
|
||||
outfit: values.outfit,
|
||||
@@ -130,13 +140,24 @@ test("avatar creation validates and persists local character choices", () => {
|
||||
height: values.height,
|
||||
});
|
||||
expect(loadAvatar({ getItem: () => "{" })).toEqual(DEFAULT_AVATAR);
|
||||
for (const race of ["saiyan", "earthling", "namekian", "majin", "frieza", "android"])
|
||||
for (const race of [
|
||||
"saiyan",
|
||||
"earthling",
|
||||
"namekian",
|
||||
"majin",
|
||||
"frieza",
|
||||
"android",
|
||||
])
|
||||
expect(normalizeAvatar({ ...values, race }).race).toBe(race);
|
||||
expect(normalizeAvatar({ ...values, build: "slim", height: 0.85 })).toMatchObject({
|
||||
expect(
|
||||
normalizeAvatar({ ...values, build: "slim", height: 0.85 }),
|
||||
).toMatchObject({
|
||||
build: "slim",
|
||||
height: 0.85,
|
||||
});
|
||||
expect(normalizeAvatar({ ...values, build: "unknown", height: 1.5 })).toMatchObject({
|
||||
expect(
|
||||
normalizeAvatar({ ...values, build: "unknown", height: 1.5 }),
|
||||
).toMatchObject({
|
||||
build: "athletic",
|
||||
height: 1.15,
|
||||
});
|
||||
@@ -179,7 +200,9 @@ test("manga series search scopes the query and filters mixed RAG results", () =>
|
||||
});
|
||||
|
||||
test("manga series search rejects unsupported source identifiers", () => {
|
||||
expect(buildLoreSearchRequest("Goku", "manga-dbs").error).toContain("n'est pas disponible");
|
||||
expect(buildLoreSearchRequest("Goku", "manga-dbs").error).toContain(
|
||||
"n'est pas disponible",
|
||||
);
|
||||
expect(buildLoreSearchRequest("x", "dragonball").error).toContain("2 et 120");
|
||||
});
|
||||
|
||||
@@ -239,7 +262,9 @@ test("Super catalogue sorts chapter IDs and rejects malformed entries", () => {
|
||||
completeStandardSet: false,
|
||||
contiguousObservedRange: false,
|
||||
});
|
||||
expect(() => normalizeLoreCatalogPayload({ volumes: [] }, "invalid")).toThrow();
|
||||
expect(() =>
|
||||
normalizeLoreCatalogPayload({ volumes: [] }, "invalid"),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("RAG coverage reports global manga passage counts without inventing missing data", () => {
|
||||
@@ -262,12 +287,15 @@ test("RAG coverage reports global manga passage counts without inventing missing
|
||||
});
|
||||
|
||||
test("multiplayer uses the local port in development and same-origin TLS in production", () => {
|
||||
expect(buildMultiplayerSocketUrl(new URL("http://localhost:4317/"), "test-room")).toBe(
|
||||
"ws://localhost:4320/ws/test-room",
|
||||
);
|
||||
expect(buildMultiplayerSocketUrl(new URL("https://game.example.com/"), "test-room")).toBe(
|
||||
"wss://game.example.com/ws/test-room",
|
||||
);
|
||||
expect(
|
||||
buildMultiplayerSocketUrl(new URL("http://localhost:4317/"), "test-room"),
|
||||
).toBe("ws://localhost:4320/ws/test-room");
|
||||
expect(
|
||||
buildMultiplayerSocketUrl(
|
||||
new URL("https://game.example.com/"),
|
||||
"test-room",
|
||||
),
|
||||
).toBe("wss://game.example.com/ws/test-room");
|
||||
expect(
|
||||
buildMultiplayerSocketUrl(
|
||||
new URL("https://game.example.com/"),
|
||||
@@ -276,7 +304,11 @@ test("multiplayer uses the local port in development and same-origin TLS in prod
|
||||
),
|
||||
).toBe("wss://socket.example.net/rooms/ws/test-room");
|
||||
expect(
|
||||
buildMultiplayerSocketUrl(new URL("https://game.example.com/"), "test-room", "/multiplayer"),
|
||||
buildMultiplayerSocketUrl(
|
||||
new URL("https://game.example.com/"),
|
||||
"test-room",
|
||||
"/multiplayer",
|
||||
),
|
||||
).toBe("wss://game.example.com/multiplayer/ws/test-room");
|
||||
expect(() =>
|
||||
buildMultiplayerSocketUrl(new URL("https://game.example.com/"), "bad room"),
|
||||
|
||||
@@ -109,8 +109,10 @@ const keys = {
|
||||
t: "transform",
|
||||
};
|
||||
for (const [key, field] of Object.entries(avatarFields))
|
||||
field.value = key === "height" ? String(Math.round(avatar.height * 100)) : avatar[key];
|
||||
document.querySelector("#avatar-height-value").value = `${Math.round(avatar.height * 100)} %`;
|
||||
field.value =
|
||||
key === "height" ? String(Math.round(avatar.height * 100)) : avatar[key];
|
||||
document.querySelector("#avatar-height-value").value =
|
||||
`${Math.round(avatar.height * 100)} %`;
|
||||
for (const field of Object.values(avatarFields)) {
|
||||
field.addEventListener("input", () => {
|
||||
const values = Object.fromEntries(
|
||||
@@ -120,7 +122,8 @@ for (const field of Object.values(avatarFields)) {
|
||||
]),
|
||||
);
|
||||
avatar = normalizeAvatar(values);
|
||||
document.querySelector("#avatar-height-value").value = `${Math.round(avatar.height * 100)} %`;
|
||||
document.querySelector("#avatar-height-value").value =
|
||||
`${Math.round(avatar.height * 100)} %`;
|
||||
try {
|
||||
avatar = saveAvatar(localStorage, avatar);
|
||||
} catch {
|
||||
@@ -152,7 +155,8 @@ document.querySelector("#pause").addEventListener("click", () => {
|
||||
document.querySelector("#join-room").addEventListener("click", async () => {
|
||||
const room = roomName.value.trim();
|
||||
if (!/^[A-Za-z0-9_-]{1,32}$/.test(room)) {
|
||||
multiplayerStatus.textContent = "Utilise 1 à 32 lettres, chiffres, tirets ou traits bas.";
|
||||
multiplayerStatus.textContent =
|
||||
"Utilise 1 à 32 lettres, chiffres, tirets ou traits bas.";
|
||||
return;
|
||||
}
|
||||
const joinButton = document.querySelector("#join-room");
|
||||
@@ -178,7 +182,9 @@ document.querySelector("#join-room").addEventListener("click", async () => {
|
||||
} catch {
|
||||
// An omitted config uses the local development port or the page's own host.
|
||||
}
|
||||
const socket = new WebSocket(buildMultiplayerSocketUrl(window.location, room, configuredBase));
|
||||
const socket = new WebSocket(
|
||||
buildMultiplayerSocketUrl(window.location, room, configuredBase),
|
||||
);
|
||||
multiplayerSocket = socket;
|
||||
socket.addEventListener("open", () => {
|
||||
if (multiplayerSocket !== socket) return;
|
||||
@@ -203,7 +209,8 @@ document.querySelector("#join-room").addEventListener("click", async () => {
|
||||
});
|
||||
socket.addEventListener("error", () => {
|
||||
if (multiplayerSocket === socket)
|
||||
multiplayerStatus.textContent = "Serveur Rust indisponible; le mode solo reste actif.";
|
||||
multiplayerStatus.textContent =
|
||||
"Serveur Rust indisponible; le mode solo reste actif.";
|
||||
});
|
||||
socket.addEventListener("close", () => {
|
||||
if (multiplayerSocket !== socket) return;
|
||||
@@ -246,7 +253,10 @@ for (const arenaCanvas of [canvas, webGpuCanvas, bevyCanvas]) {
|
||||
}
|
||||
if (event.button !== 0) return;
|
||||
const bounds = arenaCanvas.getBoundingClientRect();
|
||||
mouseTarget = Math.max(0, Math.min(960, ((event.clientX - bounds.left) / bounds.width) * 960));
|
||||
mouseTarget = Math.max(
|
||||
0,
|
||||
Math.min(960, ((event.clientX - bounds.left) / bounds.width) * 960),
|
||||
);
|
||||
});
|
||||
arenaCanvas.addEventListener("pointermove", (event) => {
|
||||
if (event.buttons & 1) {
|
||||
@@ -257,10 +267,14 @@ for (const arenaCanvas of [canvas, webGpuCanvas, bevyCanvas]) {
|
||||
);
|
||||
}
|
||||
});
|
||||
arenaCanvas.addEventListener("pointerup", (event) => held.delete(pointerAction(event.button)));
|
||||
arenaCanvas.addEventListener("pointerup", (event) =>
|
||||
held.delete(pointerAction(event.button)),
|
||||
);
|
||||
for (const event of ["pointercancel", "lostpointercapture"])
|
||||
arenaCanvas.addEventListener(event, () => held.delete("blast"));
|
||||
arenaCanvas.addEventListener("contextmenu", (event) => event.preventDefault());
|
||||
arenaCanvas.addEventListener("contextmenu", (event) =>
|
||||
event.preventDefault(),
|
||||
);
|
||||
}
|
||||
window.addEventListener("gamepadconnected", () => {
|
||||
controllerConnected = true;
|
||||
@@ -432,7 +446,8 @@ function draw() {
|
||||
canvas.hidden = false;
|
||||
}
|
||||
}
|
||||
if (!bevyApi && webGpuArena) webGpuArena.render(game, avatar, multiplayerPlayers);
|
||||
if (!bevyApi && webGpuArena)
|
||||
webGpuArena.render(game, avatar, multiplayerPlayers);
|
||||
else if (!bevyApi) draw2d();
|
||||
drawAge1000Map(mapContext, mapCanvas.width, mapCanvas.height);
|
||||
}
|
||||
@@ -441,15 +456,22 @@ function frame(now) {
|
||||
last = now;
|
||||
const gamepad = [...navigator.getGamepads()].find(Boolean);
|
||||
controllerConnected = Boolean(gamepad);
|
||||
const { input: gamepadInput, startPressed: gamepadStartPressed } = readGamepadInput(gamepad);
|
||||
const { input: gamepadInput, startPressed: gamepadStartPressed } =
|
||||
readGamepadInput(gamepad);
|
||||
let networkInput = {};
|
||||
if (gamepadStartPressed && !gamepadStartWasPressed && active && game.status === "playing")
|
||||
if (
|
||||
gamepadStartPressed &&
|
||||
!gamepadStartWasPressed &&
|
||||
active &&
|
||||
game.status === "playing"
|
||||
)
|
||||
pause(!paused);
|
||||
gamepadStartWasPressed = gamepadStartPressed;
|
||||
if (active && !paused) {
|
||||
const input = {};
|
||||
for (const action of [...held, ...pointers.values()]) input[action] = true;
|
||||
for (const [action, pressed] of Object.entries(gamepadInput)) input[action] ||= pressed;
|
||||
for (const [action, pressed] of Object.entries(gamepadInput))
|
||||
input[action] ||= pressed;
|
||||
networkInput = input;
|
||||
const mouseDirection = pointerDirection(mouseTarget, game.player.x);
|
||||
if (mouseDirection) input[mouseDirection] = true;
|
||||
@@ -475,7 +497,10 @@ function frame(now) {
|
||||
const p = game.player;
|
||||
const bevyReady = bevyApi?.bevy_renderer_ready() || false;
|
||||
hud.textContent = `${avatar.name} · Vie ${Math.ceil(p.hp)} / 100 · Énergie ${Math.floor(p.energy)} / 100 · Vague ${game.wave} / 5 · Score ${game.score}${p.transformed ? " · Transformation active" : ""} · ${bevyReady ? "Bevy 3D WebGPU" : webGpuArena ? "3D WebGPU" : "Canvas 2D"}`;
|
||||
if (multiplayerSocket?.readyState === WebSocket.OPEN && now - lastNetworkSync >= 50) {
|
||||
if (
|
||||
multiplayerSocket?.readyState === WebSocket.OPEN &&
|
||||
now - lastNetworkSync >= 50
|
||||
) {
|
||||
multiplayerSocket.send(
|
||||
JSON.stringify({
|
||||
type: "frame",
|
||||
@@ -507,7 +532,8 @@ function renderLoreResults(payload) {
|
||||
const source = document.createElement("a");
|
||||
title.textContent = entry.title || entry.entity || "Passage Dragon Ball";
|
||||
kind.textContent = entry.kind || entry.sourceId || "archive";
|
||||
passage.textContent = entry.snippet || entry.content || "Aucun extrait disponible.";
|
||||
passage.textContent =
|
||||
entry.snippet || entry.content || "Aucun extrait disponible.";
|
||||
source.href = entry.url || "https://dragonballfr.com/";
|
||||
source.textContent = "Ouvrir la source";
|
||||
source.target = "_blank";
|
||||
@@ -531,10 +557,12 @@ loreForm.addEventListener("submit", async (event) => {
|
||||
if (sourceId) params.set("sourceId", sourceId);
|
||||
const response = await fetch(`/api/lore/search?${params}`);
|
||||
const payload = await response.json();
|
||||
if (!response.ok) throw new Error(payload.error || "La recherche a échoué.");
|
||||
if (!response.ok)
|
||||
throw new Error(payload.error || "La recherche a échoué.");
|
||||
renderLoreResults(payload);
|
||||
} catch (error) {
|
||||
loreStatus.textContent = error.message || "Les archives sont momentanément indisponibles.";
|
||||
loreStatus.textContent =
|
||||
error.message || "Les archives sont momentanément indisponibles.";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -544,9 +572,12 @@ loreCatalogForm.addEventListener("submit", async (event) => {
|
||||
loreCatalogStatus.textContent = "Chargement des métadonnées de l’inventaire…";
|
||||
loreCatalogRows.replaceChildren();
|
||||
try {
|
||||
const response = await fetch(`/api/lore/catalog?series=${encodeURIComponent(series)}`);
|
||||
const response = await fetch(
|
||||
`/api/lore/catalog?series=${encodeURIComponent(series)}`,
|
||||
);
|
||||
const payload = await response.json();
|
||||
if (!response.ok) throw new Error(payload.error || "Le catalogue n’a pas pu être chargé.");
|
||||
if (!response.ok)
|
||||
throw new Error(payload.error || "Le catalogue n’a pas pu être chargé.");
|
||||
if (payload.series !== series || !Array.isArray(payload.records))
|
||||
throw new Error("La réponse du catalogue est invalide.");
|
||||
const catalog = payload;
|
||||
@@ -562,9 +593,16 @@ loreCatalogForm.addEventListener("submit", async (event) => {
|
||||
row.append(volume, pages, japanesePages);
|
||||
loreCatalogRows.append(row);
|
||||
}
|
||||
const pages = catalog.records.reduce((total, record) => total + record.pages, 0);
|
||||
const pagesWithJa = catalog.records.reduce((total, record) => total + record.pagesWithJa, 0);
|
||||
const label = series === "DB" ? "volumes Dragon Ball" : "chapitres Dragon Ball Super";
|
||||
const pages = catalog.records.reduce(
|
||||
(total, record) => total + record.pages,
|
||||
0,
|
||||
);
|
||||
const pagesWithJa = catalog.records.reduce(
|
||||
(total, record) => total + record.pagesWithJa,
|
||||
0,
|
||||
);
|
||||
const label =
|
||||
series === "DB" ? "volumes Dragon Ball" : "chapitres Dragon Ball Super";
|
||||
const omitted = catalog.omittedCount
|
||||
? ` ${catalog.omittedCount} fiche(s) hors série ou hors numérotation standard masquée(s).`
|
||||
: "";
|
||||
@@ -583,18 +621,25 @@ loreCatalogForm.addEventListener("submit", async (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector("#lore-coverage-check").addEventListener("click", async () => {
|
||||
loreCoverageStatus.textContent = "Lecture des compteurs globaux du RAG…";
|
||||
try {
|
||||
const response = await fetch("/api/lore/coverage", { cache: "no-store" });
|
||||
const coverage = await response.json();
|
||||
if (!response.ok) throw new Error(coverage.error || "La couverture n’a pas pu être chargée.");
|
||||
if (!Number.isSafeInteger(coverage.chunks))
|
||||
throw new Error("La réponse de couverture est invalide.");
|
||||
const builtAt = coverage.builtAt ? ` · index daté ${coverage.builtAt}` : "";
|
||||
loreCoverageStatus.textContent = `${formatCoverageCount(coverage.chunks)} passages au total · ${formatCoverageCount(coverage.mangaPagePassages)} pages manga · ${formatCoverageCount(coverage.mangaChapterPassages)} entrées chapitre · ${formatCoverageCount(coverage.mangaVolumePassages)} entrées volume${builtAt}. Compteurs globaux, non ventilés par série et non équivalents à une vérification OCR page par page.`;
|
||||
} catch (error) {
|
||||
loreCoverageStatus.textContent =
|
||||
error.message || "La couverture du RAG est momentanément indisponible.";
|
||||
}
|
||||
});
|
||||
document
|
||||
.querySelector("#lore-coverage-check")
|
||||
.addEventListener("click", async () => {
|
||||
loreCoverageStatus.textContent = "Lecture des compteurs globaux du RAG…";
|
||||
try {
|
||||
const response = await fetch("/api/lore/coverage", { cache: "no-store" });
|
||||
const coverage = await response.json();
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
coverage.error || "La couverture n’a pas pu être chargée.",
|
||||
);
|
||||
if (!Number.isSafeInteger(coverage.chunks))
|
||||
throw new Error("La réponse de couverture est invalide.");
|
||||
const builtAt = coverage.builtAt
|
||||
? ` · index daté ${coverage.builtAt}`
|
||||
: "";
|
||||
loreCoverageStatus.textContent = `${formatCoverageCount(coverage.chunks)} passages au total · ${formatCoverageCount(coverage.mangaPagePassages)} pages manga · ${formatCoverageCount(coverage.mangaChapterPassages)} entrées chapitre · ${formatCoverageCount(coverage.mangaVolumePassages)} entrées volume${builtAt}. Compteurs globaux, non ventilés par série et non équivalents à une vérification OCR page par page.`;
|
||||
} catch (error) {
|
||||
loreCoverageStatus.textContent =
|
||||
error.message || "La couverture du RAG est momentanément indisponible.";
|
||||
}
|
||||
});
|
||||
@@ -12,8 +12,8 @@
|
||||
<p class="eyebrow">Le mini-jeu d'Omar · Prototype original</p>
|
||||
<h1>Dragon <span>Pixel</span></h1>
|
||||
<p>
|
||||
Charge ton énergie. Transforme-toi. Repousse cinq vagues de guerriers; clique dans l’arène
|
||||
pour te déplacer.
|
||||
Charge ton énergie. Transforme-toi. Repousse cinq vagues de guerriers;
|
||||
clique dans l’arène pour te déplacer.
|
||||
</p>
|
||||
</header>
|
||||
<main>
|
||||
@@ -47,10 +47,18 @@
|
||||
<div>
|
||||
<p class="eyebrow">Atelier de personnage · sauvegarde locale</p>
|
||||
<h2 id="avatar-title">Créer ton avatar</h2>
|
||||
<p>Personnage original : ces choix restent dans le stockage local de ce navigateur.</p>
|
||||
<p>
|
||||
Personnage original : ces choix restent dans le stockage local de ce
|
||||
navigateur.
|
||||
</p>
|
||||
</div>
|
||||
<label for="avatar-name">Nom</label>
|
||||
<input id="avatar-name" maxlength="20" value="Patrouilleur" autocomplete="off" />
|
||||
<input
|
||||
id="avatar-name"
|
||||
maxlength="20"
|
||||
value="Patrouilleur"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label for="avatar-race">Race</label>
|
||||
<select id="avatar-race">
|
||||
<option value="saiyan">Saiyan</option>
|
||||
@@ -72,11 +80,15 @@
|
||||
<option value="athletic">Athlétique</option>
|
||||
<option value="broad">Robuste</option>
|
||||
</select>
|
||||
<label for="avatar-height">Taille <output id="avatar-height-value">100 %</output></label>
|
||||
<label for="avatar-height"
|
||||
>Taille <output id="avatar-height-value">100 %</output></label
|
||||
>
|
||||
<input id="avatar-height" type="range" min="85" max="115" value="100" />
|
||||
<label for="avatar-skin">Peau</label><input id="avatar-skin" type="color" value="#f0c19a" />
|
||||
<label for="avatar-skin">Peau</label
|
||||
><input id="avatar-skin" type="color" value="#f0c19a" />
|
||||
<label for="avatar-hair">Cheveux</label
|
||||
><input id="avatar-hair" type="color" value="#242b46" /> <label for="color">Tenue</label
|
||||
><input id="avatar-hair" type="color" value="#242b46" />
|
||||
<label for="color">Tenue</label
|
||||
><input id="color" type="color" value="#63e6ff" />
|
||||
</section>
|
||||
<p id="hud">Prêt à entrer dans l'arène.</p>
|
||||
@@ -105,16 +117,17 @@
|
||||
hidden
|
||||
></canvas>
|
||||
<p class="render-note">
|
||||
Rendu 3D Bevy/WebGPU si disponible; repli WebGPU ou Canvas 2D sinon. J/K/L/T ou les boutons
|
||||
à l’écran servent à combattre.
|
||||
Rendu 3D Bevy/WebGPU si disponible; repli WebGPU ou Canvas 2D sinon.
|
||||
J/K/L/T ou les boutons à l’écran servent à combattre.
|
||||
</p>
|
||||
<section class="world-map" aria-labelledby="world-map-title">
|
||||
<div>
|
||||
<p class="eyebrow">Carte d’expédition · AGE 1000</p>
|
||||
<h2 id="world-map-title">West City et la porte glaciaire</h2>
|
||||
<p>
|
||||
Schéma original inspiré des lieux décrits publiquement : campus, quartiers urbains,
|
||||
complexe Capsule et point de départ vers les missions.
|
||||
Schéma original inspiré des lieux décrits publiquement : campus,
|
||||
quartiers urbains, complexe Capsule et point de départ vers les
|
||||
missions.
|
||||
</p>
|
||||
</div>
|
||||
<canvas
|
||||
@@ -125,8 +138,8 @@
|
||||
aria-label="Carte schématique originale de West City en Age 1000 avec campus, quartier Capsule, centre-ville, esplanade et avant-poste glaciaire."
|
||||
></canvas>
|
||||
<small
|
||||
>Carte de prototype dessinée pour Dragon Pixel, sans captures ni ressources
|
||||
extraites.</small
|
||||
>Carte de prototype dessinée pour Dragon Pixel, sans captures ni
|
||||
ressources extraites.</small
|
||||
>
|
||||
</section>
|
||||
<section class="lore" aria-labelledby="lore-title">
|
||||
@@ -134,9 +147,9 @@
|
||||
<p class="eyebrow">Archives Shenron · lecture en direct</p>
|
||||
<h2 id="lore-title">Explorer les archives Dragon Ball</h2>
|
||||
<p>
|
||||
Recherche dans le corpus Dragon Ball accessible par le service Shenron. Les résultats
|
||||
restent de courts extraits sourcés ; les planches et images ne sont pas copiées dans le
|
||||
jeu.
|
||||
Recherche dans le corpus Dragon Ball accessible par le service
|
||||
Shenron. Les résultats restent de courts extraits sourcés ; les
|
||||
planches et images ne sont pas copiées dans le jeu.
|
||||
</p>
|
||||
</div>
|
||||
<form id="lore-search">
|
||||
@@ -153,7 +166,9 @@
|
||||
<label for="lore-source">Corpus</label>
|
||||
<select id="lore-source" name="sourceId">
|
||||
<option value="">Tout le RAG Dragon Ball</option>
|
||||
<option value="manga">Pages OCR des mangas (Dragon Ball et Super)</option>
|
||||
<option value="manga">
|
||||
Pages OCR des mangas (Dragon Ball et Super)
|
||||
</option>
|
||||
<option value="dragonball">Dragon Ball — 42 tomes</option>
|
||||
<option value="dragonballsuper">Dragon Ball Super</option>
|
||||
</select>
|
||||
@@ -165,10 +180,12 @@
|
||||
<ol id="lore-results" aria-label="Résultats des archives"></ol>
|
||||
<div class="lore-coverage">
|
||||
<h3>Mesurer la couverture du RAG</h3>
|
||||
<button id="lore-coverage-check" type="button">Vérifier l’index Shenron</button>
|
||||
<button id="lore-coverage-check" type="button">
|
||||
Vérifier l’index Shenron
|
||||
</button>
|
||||
<p id="lore-coverage-status" role="status" aria-live="polite">
|
||||
Les compteurs globaux ne prouvent pas la transcription complète de chaque tome ou
|
||||
chapitre.
|
||||
Les compteurs globaux ne prouvent pas la transcription complète de
|
||||
chaque tome ou chapitre.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lore-catalog">
|
||||
@@ -182,8 +199,8 @@
|
||||
<button type="submit">Afficher l’inventaire</button>
|
||||
</form>
|
||||
<p id="lore-catalog-status" role="status" aria-live="polite">
|
||||
L’inventaire indique les pages déclarées par Shenron; il ne certifie pas une
|
||||
transcription OCR complète de chaque page.
|
||||
L’inventaire indique les pages déclarées par Shenron; il ne certifie
|
||||
pas une transcription OCR complète de chaque page.
|
||||
</p>
|
||||
<div
|
||||
class="lore-table-wrap"
|
||||
@@ -209,26 +226,30 @@
|
||||
</section>
|
||||
<p id="result" role="status">Choisis ton style puis lance la partie.</p>
|
||||
<div class="controls" aria-label="Commandes tactiles">
|
||||
<button data-action="left">← Gauche</button><button data-action="right">Droite →</button
|
||||
><button data-action="jump">Saut</button><button data-action="punch">Combo</button
|
||||
><button data-action="blast">Projectile</button><button data-action="charge">Charger</button
|
||||
<button data-action="left">← Gauche</button
|
||||
><button data-action="right">Droite →</button
|
||||
><button data-action="jump">Saut</button
|
||||
><button data-action="punch">Combo</button
|
||||
><button data-action="blast">Projectile</button
|
||||
><button data-action="charge">Charger</button
|
||||
><button data-action="transform">Transformer</button>
|
||||
</div>
|
||||
<details>
|
||||
<summary>Comment jouer</summary>
|
||||
<p>
|
||||
Flèches gauche/droite ou Q/D : déplacement · clic gauche/glisser : bouger · clic droit :
|
||||
tir · Espace : saut · J : combo · K : projectile (20 énergie) · L : charge · T :
|
||||
transformation (80 énergie, 8 secondes) · P : pause. Sur manette : stick gauche ou croix
|
||||
pour bouger, A sauter, X combo, B projectile, Y charger, Start pause/reprise. Sur mobile,
|
||||
maintiens les boutons. Élimine cinq vagues pour gagner. Les styles changent la vitesse ou
|
||||
la puissance.
|
||||
Flèches gauche/droite ou Q/D : déplacement · clic gauche/glisser :
|
||||
bouger · clic droit : tir · Espace : saut · J : combo · K : projectile
|
||||
(20 énergie) · L : charge · T : transformation (80 énergie, 8
|
||||
secondes) · P : pause. Sur manette : stick gauche ou croix pour
|
||||
bouger, A sauter, X combo, B projectile, Y charger, Start
|
||||
pause/reprise. Sur mobile, maintiens les boutons. Élimine cinq vagues
|
||||
pour gagner. Les styles changent la vitesse ou la puissance.
|
||||
</p>
|
||||
</details>
|
||||
</main>
|
||||
<footer>
|
||||
Inspiré des mécaniques de Dragon Crystal et Warriors of the Universe. Personnages et
|
||||
graphismes originaux. Solo contre IA, sans compte.
|
||||
Inspiré des mécaniques de Dragon Crystal et Warriors of the Universe.
|
||||
Personnages et graphismes originaux. Solo contre IA, sans compte.
|
||||
</footer>
|
||||
<script type="module" src="game.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -4,7 +4,9 @@ const catalogSeries = new Set(["DB", "DBS"]);
|
||||
export function buildLoreCatalogRequest(series) {
|
||||
const normalizedSeries = typeof series === "string" ? series.trim() : "";
|
||||
if (!catalogSeries.has(normalizedSeries))
|
||||
return { error: "Choisis Dragon Ball ou Dragon Ball Super dans le catalogue." };
|
||||
return {
|
||||
error: "Choisis Dragon Ball ou Dragon Ball Super dans le catalogue.",
|
||||
};
|
||||
return { series: normalizedSeries };
|
||||
}
|
||||
|
||||
@@ -46,11 +48,18 @@ export function normalizeLoreCatalogPayload(payload, series) {
|
||||
? Array.from({ length: 42 }, (_, index) => index + 1)
|
||||
: firstNumber === null
|
||||
? []
|
||||
: Array.from({ length: lastNumber - firstNumber + 1 }, (_, index) => firstNumber + index);
|
||||
const missingNumbers = expectedNumbers.filter((number) => !observedNumbers.includes(number));
|
||||
: Array.from(
|
||||
{ length: lastNumber - firstNumber + 1 },
|
||||
(_, index) => firstNumber + index,
|
||||
);
|
||||
const missingNumbers = expectedNumbers.filter(
|
||||
(number) => !observedNumbers.includes(number),
|
||||
);
|
||||
return {
|
||||
series,
|
||||
declaredCount: Number.isSafeInteger(payload.count) ? payload.count : payload.volumes.length,
|
||||
declaredCount: Number.isSafeInteger(payload.count)
|
||||
? payload.count
|
||||
: payload.volumes.length,
|
||||
omittedCount: payload.volumes.length - records.length,
|
||||
coverage: {
|
||||
observedCount: observedNumbers.length,
|
||||
@@ -58,14 +67,18 @@ export function normalizeLoreCatalogPayload(payload, series) {
|
||||
lastNumber,
|
||||
missingNumbers,
|
||||
completeStandardSet: series === "DB" && missingNumbers.length === 0,
|
||||
contiguousObservedRange: firstNumber !== null && missingNumbers.length === 0,
|
||||
contiguousObservedRange:
|
||||
firstNumber !== null && missingNumbers.length === 0,
|
||||
},
|
||||
records,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeLoreCoveragePayload(payload) {
|
||||
if (!Number.isSafeInteger(payload?.index?.chunks) || !Array.isArray(payload?.sources))
|
||||
if (
|
||||
!Number.isSafeInteger(payload?.index?.chunks) ||
|
||||
!Array.isArray(payload?.sources)
|
||||
)
|
||||
throw new TypeError("Réponse de couverture RAG invalide.");
|
||||
const sourceCount = (sourceId, kind) => {
|
||||
const entry = payload.sources.find(
|
||||
@@ -75,7 +88,8 @@ export function normalizeLoreCoveragePayload(payload) {
|
||||
};
|
||||
return {
|
||||
chunks: payload.index.chunks,
|
||||
builtAt: typeof payload.index.builtAt === "string" ? payload.index.builtAt : null,
|
||||
builtAt:
|
||||
typeof payload.index.builtAt === "string" ? payload.index.builtAt : null,
|
||||
mangaPagePassages: sourceCount("manga", "manga_page"),
|
||||
mangaChapterPassages: sourceCount("db_manga_chapters", "manga_chapter"),
|
||||
mangaVolumePassages: sourceCount("db_manga_volumes", "manga_volume"),
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
const series = {
|
||||
"": { queryPrefix: "", titlePrefix: null },
|
||||
manga: { queryPrefix: "", titlePrefix: null },
|
||||
dragonball: { queryPrefix: "Dragon Ball tome", titlePrefix: "Dragon Ball — tome " },
|
||||
dragonball: {
|
||||
queryPrefix: "Dragon Ball tome",
|
||||
titlePrefix: "Dragon Ball — tome ",
|
||||
},
|
||||
dragonballsuper: {
|
||||
queryPrefix: "Dragon Ball Super",
|
||||
titlePrefix: "Dragon Ball Super — chapitre ",
|
||||
@@ -15,7 +18,8 @@ export function buildLoreSearchRequest(query, sourceId) {
|
||||
if (normalizedQuery.length < 2 || normalizedQuery.length > 120)
|
||||
return { error: "La recherche doit contenir entre 2 et 120 caractères." };
|
||||
const selected = series[normalizedSource];
|
||||
if (!selected) return { error: "Le corpus sélectionné n'est pas disponible." };
|
||||
if (!selected)
|
||||
return { error: "Le corpus sélectionné n'est pas disponible." };
|
||||
return {
|
||||
query: [selected.queryPrefix, normalizedQuery].filter(Boolean).join(" "),
|
||||
sourceId: normalizedSource ? "manga" : "",
|
||||
@@ -34,6 +38,8 @@ export function filterLoreSearchPayload(payload, sourceId) {
|
||||
: [];
|
||||
return {
|
||||
...payload,
|
||||
results: items.filter((item) => item?.title?.startsWith(titlePrefix)).slice(0, 8),
|
||||
results: items
|
||||
.filter((item) => item?.title?.startsWith(titlePrefix))
|
||||
.slice(0, 8),
|
||||
};
|
||||
}
|
||||
@@ -6,9 +6,12 @@ export function buildMultiplayerSocketUrl(location, room, configuredBase = "") {
|
||||
throw new TypeError("Le nom de salle est invalide.");
|
||||
const page = new URL(location.href);
|
||||
if (page.protocol !== "http:" && page.protocol !== "https:")
|
||||
throw new TypeError("Le protocole de la page ne permet pas les WebSockets.");
|
||||
throw new TypeError(
|
||||
"Le protocole de la page ne permet pas les WebSockets.",
|
||||
);
|
||||
const socketProtocol = page.protocol === "https:" ? "wss:" : "ws:";
|
||||
const explicitBase = typeof configuredBase === "string" ? configuredBase.trim() : "";
|
||||
const explicitBase =
|
||||
typeof configuredBase === "string" ? configuredBase.trim() : "";
|
||||
let socket;
|
||||
if (explicitBase) {
|
||||
socket = new URL(explicitBase, `${socketProtocol}//${page.host}`);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { buildLoreSearchRequest, filterLoreSearchPayload } from "./lore-search.js";
|
||||
import {
|
||||
buildLoreSearchRequest,
|
||||
filterLoreSearchPayload,
|
||||
} from "./lore-search.js";
|
||||
import {
|
||||
buildLoreCatalogRequest,
|
||||
normalizeLoreCatalogPayload,
|
||||
@@ -20,7 +23,8 @@ const files = new Set([
|
||||
"dragon-pixel-wasm.js",
|
||||
"dragon-pixel-wasm_bg.wasm",
|
||||
]);
|
||||
const shenronBase = process.env.SHENRON_API_BASE || "https://bot.dragonballfr.com";
|
||||
const shenronBase =
|
||||
process.env.SHENRON_API_BASE || "https://bot.dragonballfr.com";
|
||||
function publicMultiplayerWebSocketBase() {
|
||||
const value = process.env.DRAGON_PIXEL_WS_BASE?.trim() || "";
|
||||
if (!value) return "";
|
||||
@@ -52,7 +56,10 @@ const server = Bun.serve({
|
||||
{ multiplayerWebSocketBase: publicMultiplayerWebSocketBase() },
|
||||
{ headers: { "cache-control": "no-store" } },
|
||||
);
|
||||
if (pathname === "/dragon-pixel-wasm.js" || pathname === "/dragon-pixel-wasm_bg.wasm") {
|
||||
if (
|
||||
pathname === "/dragon-pixel-wasm.js" ||
|
||||
pathname === "/dragon-pixel-wasm_bg.wasm"
|
||||
) {
|
||||
const file = Bun.file(
|
||||
new URL(
|
||||
`../../../target/wasm-package-stage/aphrody-dragon-pixel-wasm/${pathname.slice(1)}`,
|
||||
@@ -62,7 +69,9 @@ const server = Bun.serve({
|
||||
return file.exists().then((exists) =>
|
||||
exists
|
||||
? new Response(file, {
|
||||
headers: pathname.endsWith(".wasm") ? { "content-type": "application/wasm" } : {},
|
||||
headers: pathname.endsWith(".wasm")
|
||||
? { "content-type": "application/wasm" }
|
||||
: {},
|
||||
})
|
||||
: new Response("WASM package not built", { status: 404 }),
|
||||
);
|
||||
@@ -71,7 +80,8 @@ const server = Bun.serve({
|
||||
const params = new URL(request.url).searchParams;
|
||||
const sourceId = params.get("sourceId")?.trim() || "";
|
||||
const search = buildLoreSearchRequest(params.get("q"), sourceId);
|
||||
if (search.error) return Response.json({ error: search.error }, { status: 400 });
|
||||
if (search.error)
|
||||
return Response.json({ error: search.error }, { status: 400 });
|
||||
const url = new URL("/api/public/rag/search", shenronBase);
|
||||
url.searchParams.set("q", search.query);
|
||||
url.searchParams.set("limit", String(search.limit));
|
||||
@@ -87,7 +97,8 @@ const server = Bun.serve({
|
||||
status: response.status,
|
||||
headers: {
|
||||
"content-type":
|
||||
response.headers.get("content-type") || "application/json; charset=utf-8",
|
||||
response.headers.get("content-type") ||
|
||||
"application/json; charset=utf-8",
|
||||
"cache-control": "no-store",
|
||||
},
|
||||
});
|
||||
@@ -100,10 +111,13 @@ const server = Bun.serve({
|
||||
},
|
||||
});
|
||||
try {
|
||||
return Response.json(filterLoreSearchPayload(JSON.parse(body), sourceId), {
|
||||
status: response.status,
|
||||
headers: { "cache-control": "no-store" },
|
||||
});
|
||||
return Response.json(
|
||||
filterLoreSearchPayload(JSON.parse(body), sourceId),
|
||||
{
|
||||
status: response.status,
|
||||
headers: { "cache-control": "no-store" },
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ error: "La réponse des archives est illisible." },
|
||||
@@ -121,7 +135,8 @@ const server = Bun.serve({
|
||||
if (name === "api/lore/catalog" && request.method === "GET") {
|
||||
const params = new URL(request.url).searchParams;
|
||||
const catalog = buildLoreCatalogRequest(params.get("series"));
|
||||
if (catalog.error) return Response.json({ error: catalog.error }, { status: 400 });
|
||||
if (catalog.error)
|
||||
return Response.json({ error: catalog.error }, { status: 400 });
|
||||
const url = new URL("/api/public/manga/tomes", shenronBase);
|
||||
url.searchParams.set("series", catalog.series);
|
||||
return fetch(url, {
|
||||
@@ -139,9 +154,12 @@ const server = Bun.serve({
|
||||
},
|
||||
});
|
||||
try {
|
||||
return Response.json(normalizeLoreCatalogPayload(JSON.parse(body), catalog.series), {
|
||||
headers: { "cache-control": "no-store" },
|
||||
});
|
||||
return Response.json(
|
||||
normalizeLoreCatalogPayload(JSON.parse(body), catalog.series),
|
||||
{
|
||||
headers: { "cache-control": "no-store" },
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ error: "La réponse du catalogue est illisible." },
|
||||
@@ -168,9 +186,12 @@ const server = Bun.serve({
|
||||
{ status: 502, headers: { "cache-control": "no-store" } },
|
||||
);
|
||||
try {
|
||||
return Response.json(normalizeLoreCoveragePayload(await response.json()), {
|
||||
headers: { "cache-control": "no-store" },
|
||||
});
|
||||
return Response.json(
|
||||
normalizeLoreCoveragePayload(await response.json()),
|
||||
{
|
||||
headers: { "cache-control": "no-store" },
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ error: "La réponse de couverture RAG est illisible." },
|
||||
@@ -180,7 +201,10 @@ const server = Bun.serve({
|
||||
})
|
||||
.catch(() =>
|
||||
Response.json(
|
||||
{ error: "La couverture publique du RAG est momentanément indisponible." },
|
||||
{
|
||||
error:
|
||||
"La couverture publique du RAG est momentanément indisponible.",
|
||||
},
|
||||
{ status: 502, headers: { "cache-control": "no-store" } },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -24,4 +24,3 @@ chargeur d'extensions d'une application.
|
||||
La qualification Linux et ses limites sont consignées dans
|
||||
[le reçu](../../../docs/aphrody/operations/workspace/rust-extension-2026-10-07.md).
|
||||
Les artefacts SDK et leur provenance restent dans leur store existant.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This action syncs "studio-staging" with "main" by force pushing every time it is run.
|
||||
# Adapted from:
|
||||
# Adapted from:
|
||||
# https://github.community/t/user-github-pages-site-auto-deploy-from-another-branch-on-push/17912/2
|
||||
on:
|
||||
push:
|
||||
@@ -22,12 +22,11 @@ jobs:
|
||||
- name: push changes
|
||||
uses: ad-m/github-push-action@0fafdd62b84042d49ec0cb92d9cac7f7ce4ec79e
|
||||
with:
|
||||
# Note: Github does not allow the default access token to push to
|
||||
# protected branches. Using a PAT with permissions can bypass this,
|
||||
# Note: Github does not allow the default access token to push to
|
||||
# protected branches. Using a PAT with permissions can bypass this,
|
||||
# but it triggers recursive Github Actions calls on branch updates
|
||||
# (which the default access token does not). Ultimately, it's better
|
||||
# to leave the studio-staging branch unprotected.
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: studio-staging
|
||||
force: true
|
||||
|
||||
@@ -1,45 +1,57 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>ArtCraft</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<head>
|
||||
<title>ArtCraft</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-Y4Y8KMLPQ7"
|
||||
></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
gtag("config", "G-Y4Y8KMLPQ7");
|
||||
</script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Y4Y8KMLPQ7"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Y4Y8KMLPQ7');
|
||||
</script>
|
||||
<!---->
|
||||
<!-- <script crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js"></script> -->
|
||||
|
||||
<!---->
|
||||
<!-- <script crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js"></script> -->
|
||||
<!-- Microsoft Clarity -->
|
||||
<script type="text/javascript">
|
||||
(function (c, l, a, r, i, t, y) {
|
||||
c[a] =
|
||||
c[a] ||
|
||||
function () {
|
||||
(c[a].q = c[a].q || []).push(arguments);
|
||||
};
|
||||
t = l.createElement(r);
|
||||
t.async = 1;
|
||||
t.src = "https://www.clarity.ms/tag/" + i;
|
||||
y = l.getElementsByTagName(r)[0];
|
||||
y.parentNode.insertBefore(t, y);
|
||||
})(window, document, "clarity", "script", "sixp9uu263");
|
||||
</script>
|
||||
|
||||
<!-- Microsoft Clarity -->
|
||||
<script type="text/javascript">
|
||||
(function (c, l, a, r, i, t, y) {
|
||||
c[a] = c[a] || function () {(c[a].q = c[a].q || []).push(arguments)};
|
||||
t = l.createElement(r); t.async = 1; t.src = "https://www.clarity.ms/tag/" + i;
|
||||
y = l.getElementsByTagName(r)[0]; y.parentNode.insertBefore(t, y);
|
||||
})(window, document, "clarity", "script", "sixp9uu263");
|
||||
</script>
|
||||
|
||||
<base href="/" />
|
||||
<link rel="icon" type="image/svg+xml" href="/artcraft-icon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,100..900&family=Instrument+Serif:ital@0;1&family=Geist+Mono:wght@300..800&display=swap"
|
||||
rel="stylesheet" />
|
||||
<!-- <link rel="stylesheet" href="/src/styles.css" /> -->
|
||||
</head>
|
||||
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
<base href="/" />
|
||||
<link rel="icon" type="image/svg+xml" href="/artcraft-icon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,100..900&family=Instrument+Serif:ital@0;1&family=Geist+Mono:wght@300..800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!-- <link rel="stylesheet" href="/src/styles.css" /> -->
|
||||
</head>
|
||||
|
||||
<body className="overflow-hidden bg-ui-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,12 @@
|
||||
|
||||
/**
|
||||
* This is cached whenever vite/remix/whatever launches.
|
||||
* It's fed from an environment variable.
|
||||
*/
|
||||
export enum BuildEnvironmentType {
|
||||
Dev,
|
||||
DevProxy,
|
||||
Staging,
|
||||
Production,
|
||||
Dev,
|
||||
DevProxy,
|
||||
Staging,
|
||||
Production,
|
||||
}
|
||||
|
||||
export class BuildEnvironment {
|
||||
@@ -20,10 +19,15 @@ export class BuildEnvironment {
|
||||
}
|
||||
|
||||
// https://v2.vitejs.dev/guide/env-and-mode.html
|
||||
console.log('Reading environment variable: `VITE_ENVIRONMENT_TYPE` to determine build environment.')
|
||||
console.log(`Environment variable 'VITE_ENVIRONMENT_TYPE' = ${import.meta.env.VITE_ENVIRONMENT_TYPE}`)
|
||||
console.log(
|
||||
"Reading environment variable: `VITE_ENVIRONMENT_TYPE` to determine build environment.",
|
||||
);
|
||||
console.log(
|
||||
`Environment variable 'VITE_ENVIRONMENT_TYPE' = ${import.meta.env.VITE_ENVIRONMENT_TYPE}`,
|
||||
);
|
||||
|
||||
const environmentLabel : string | undefined = import.meta.env.VITE_ENVIRONMENT_TYPE;
|
||||
const environmentLabel: string | undefined = import.meta.env
|
||||
.VITE_ENVIRONMENT_TYPE;
|
||||
|
||||
let environmentType = BuildEnvironmentType.Production;
|
||||
|
||||
@@ -56,6 +60,6 @@ export class BuildEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
export function GetBuildEnvironment() : BuildEnvironment {
|
||||
export function GetBuildEnvironment(): BuildEnvironment {
|
||||
return BuildEnvironment.getInstance();
|
||||
}
|
||||
@@ -3,7 +3,8 @@ import { StorytellerApiHostStore } from "@storyteller/api";
|
||||
|
||||
export async function get_media_url(mediaId: string) {
|
||||
//This is for prod when we have the proper info on the url.
|
||||
const apiSchemeAndHost = StorytellerApiHostStore.getInstance().getApiSchemeAndHost();
|
||||
const apiSchemeAndHost =
|
||||
StorytellerApiHostStore.getInstance().getApiSchemeAndHost();
|
||||
const url = `${apiSchemeAndHost}/v1/media_files/file/${mediaId}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
@@ -190,7 +190,9 @@ export class ApiManager {
|
||||
);
|
||||
}
|
||||
|
||||
protected parseBodyValues<T extends object, B extends object>(params: NonNullableObject<T>): B {
|
||||
protected parseBodyValues<T extends object, B extends object>(
|
||||
params: NonNullableObject<T>,
|
||||
): B {
|
||||
return Object.entries(params).reduce((allParams, [key, value]) => {
|
||||
if (!value) {
|
||||
return allParams;
|
||||
|
||||
@@ -85,5 +85,4 @@ export class EngineApi extends ApiManager {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export class JobsApi extends ApiManager {
|
||||
|
||||
public ListRecentJobs(): Promise<ApiResponse<Job[]>> {
|
||||
const endpoint = `${this.getApiSchemeAndHost()}/v1/jobs/session`;
|
||||
|
||||
|
||||
return this.get<{
|
||||
success: boolean;
|
||||
jobs: Job[];
|
||||
|
||||
@@ -8,7 +8,11 @@ import { ApiManager, ApiResponse } from "./ApiManager";
|
||||
import { authentication } from "~/signals";
|
||||
import { Visibility } from "~/enums";
|
||||
|
||||
import { FilterEngineCategories, FilterMediaClasses, FilterMediaType } from "~/enums";
|
||||
import {
|
||||
FilterEngineCategories,
|
||||
FilterMediaClasses,
|
||||
FilterMediaType,
|
||||
} from "~/enums";
|
||||
|
||||
interface ListMediaQuery {
|
||||
sort_ascending?: boolean;
|
||||
@@ -89,7 +93,9 @@ export class MediaFilesApi extends ApiManager {
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
filter_media_type: query.filter_media_type ? query.filter_media_type.join(",") : undefined,
|
||||
filter_media_type: query.filter_media_type
|
||||
? query.filter_media_type.join(",")
|
||||
: undefined,
|
||||
filter_engine_categories: query.filter_engine_categories
|
||||
? query.filter_engine_categories.join(",")
|
||||
: undefined,
|
||||
@@ -121,7 +127,9 @@ export class MediaFilesApi extends ApiManager {
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
filter_media_type: query.filter_media_type ? query.filter_media_type.join(",") : undefined,
|
||||
filter_media_type: query.filter_media_type
|
||||
? query.filter_media_type.join(",")
|
||||
: undefined,
|
||||
filter_engine_categories: query.filter_engine_categories
|
||||
? query.filter_engine_categories.join(",")
|
||||
: undefined,
|
||||
@@ -155,7 +163,9 @@ export class MediaFilesApi extends ApiManager {
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
filter_media_type: query.filter_media_type ? query.filter_media_type.join(",") : undefined,
|
||||
filter_media_type: query.filter_media_type
|
||||
? query.filter_media_type.join(",")
|
||||
: undefined,
|
||||
filter_engine_categories: query.filter_engine_categories
|
||||
? query.filter_engine_categories.join(",")
|
||||
: undefined,
|
||||
@@ -187,7 +197,9 @@ export class MediaFilesApi extends ApiManager {
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
filter_media_type: query.filter_media_type ? query.filter_media_type.join(",") : undefined,
|
||||
filter_media_type: query.filter_media_type
|
||||
? query.filter_media_type.join(",")
|
||||
: undefined,
|
||||
filter_engine_categories: query.filter_engine_categories
|
||||
? query.filter_engine_categories.join(",")
|
||||
: undefined,
|
||||
@@ -217,7 +229,9 @@ export class MediaFilesApi extends ApiManager {
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
filter_media_type: query.filter_media_type ? query.filter_media_type.join(",") : undefined,
|
||||
filter_media_type: query.filter_media_type
|
||||
? query.filter_media_type.join(",")
|
||||
: undefined,
|
||||
filter_engine_categories: query.filter_engine_categories
|
||||
? query.filter_engine_categories.join(",")
|
||||
: undefined,
|
||||
|
||||
@@ -5,7 +5,9 @@ import { FilterEngineCategories, Visibility } from "~/enums";
|
||||
export class MediaUploadApi extends ApiManager {
|
||||
private sessionToken?: string;
|
||||
|
||||
private async getSessionTokenForUploadStudioShot(): Promise<ApiResponse<string>> {
|
||||
private async getSessionTokenForUploadStudioShot(): Promise<
|
||||
ApiResponse<string>
|
||||
> {
|
||||
const endpoint = `${this.getApiSchemeAndHost()}/v1/session_token`;
|
||||
return await this.get<{
|
||||
maybe_signed_session: string;
|
||||
@@ -177,7 +179,8 @@ export class MediaUploadApi extends ApiManager {
|
||||
}): Promise<ApiResponse<string>> {
|
||||
const endpoint = `${this.getApiSchemeAndHost()}/v1/media_files/upload/image`;
|
||||
const options: Record<string, string | number | undefined> = {
|
||||
is_intermediate_system_file: EIntermediateFile[is_intermediate_system_file],
|
||||
is_intermediate_system_file:
|
||||
EIntermediateFile[is_intermediate_system_file],
|
||||
maybe_title,
|
||||
maybe_visibility: maybe_visibility?.toString(),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { ApiManager, ApiResponse } from "./ApiManager";
|
||||
import { Visibility } from "~/enums";
|
||||
|
||||
|
||||
@@ -85,5 +85,4 @@ export class UserBookmarksApi extends ApiManager {
|
||||
return { success: false, errorMessage: err.message };
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@ interface SignupRequest {
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
signup_source?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class UsersApi extends ApiManager {
|
||||
public GetSession(): Promise<
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
|
||||
export const normalizeCanvas = (canvas: HTMLCanvasElement, width: number, height: number): OffscreenCanvas => {
|
||||
export const normalizeCanvas = (
|
||||
canvas: HTMLCanvasElement,
|
||||
width: number,
|
||||
height: number,
|
||||
): OffscreenCanvas => {
|
||||
const newCanvas = new OffscreenCanvas(width, height);
|
||||
newCanvas.width = width;
|
||||
newCanvas.height = height;
|
||||
@@ -12,9 +15,13 @@ export const normalizeCanvas = (canvas: HTMLCanvasElement, width: number, height
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.drawImage(canvas, 0, 0, width, height);
|
||||
return newCanvas;
|
||||
}
|
||||
};
|
||||
|
||||
export const drawAlphaMask = (canvas: OffscreenCanvas, width: number, height: number) => {
|
||||
export const drawAlphaMask = (
|
||||
canvas: OffscreenCanvas,
|
||||
width: number,
|
||||
height: number,
|
||||
) => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) {
|
||||
throw new Error("Failed to get canvas context");
|
||||
@@ -26,4 +33,4 @@ export const drawAlphaMask = (canvas: OffscreenCanvas, width: number, height: nu
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Return the CDN origin (scheme + host + optional port, but no path)
|
||||
* See: https://web.dev/articles/url-parts
|
||||
*
|
||||
*
|
||||
* TODO(bt,2025-02-10): Make this easier to configure in development
|
||||
*/
|
||||
class CdnHostHelper {
|
||||
@@ -15,7 +15,7 @@ class CdnHostHelper {
|
||||
return CdnHostHelper.instance;
|
||||
}
|
||||
|
||||
const origin = 'https://cdn-2.fakeyou.com';
|
||||
const origin = "https://cdn-2.fakeyou.com";
|
||||
const instance = new CdnHostHelper(origin);
|
||||
|
||||
CdnHostHelper.instance = instance;
|
||||
@@ -32,6 +32,6 @@ class CdnHostHelper {
|
||||
}
|
||||
|
||||
// TODO(bt,2025-02-10): Consolidate all of this logic.
|
||||
export function GetCdnOrigin() : string {
|
||||
export function GetCdnOrigin(): string {
|
||||
return CdnHostHelper.getInstance().getOrigin();
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class Cache {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public canCall() : boolean {
|
||||
public canCall(): boolean {
|
||||
if (this.lastFetchSuccess === undefined) {
|
||||
return true;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class Cache {
|
||||
* Runs any necessary user session functions if things change.
|
||||
*/
|
||||
export const SyncStorytellerApiConfig = async () => {
|
||||
console.log("SyncStorytellerApiConfig()")
|
||||
console.log("SyncStorytellerApiConfig()");
|
||||
|
||||
const cache = Cache.getInstance();
|
||||
const oldValue = StorytellerApiHostStore.getInstance().getApiSchemeAndHost();
|
||||
@@ -52,7 +52,7 @@ export const SyncStorytellerApiConfig = async () => {
|
||||
|
||||
const schemeAndHost = appInfo.payload.storyteller_host;
|
||||
if (!schemeAndHost) {
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Updating hostname to ${schemeAndHost}`);
|
||||
@@ -61,10 +61,10 @@ export const SyncStorytellerApiConfig = async () => {
|
||||
cache.setCallSuccess();
|
||||
|
||||
if (oldValue !== schemeAndHost) {
|
||||
console.log("SyncStorytellerApiConfig() - force session refresh")
|
||||
// NB: This is a hack that might prevent the login screen from being shown
|
||||
console.log("SyncStorytellerApiConfig() - force session refresh");
|
||||
// NB: This is a hack that might prevent the login screen from being shown
|
||||
// if the user is working in development.
|
||||
await forceGetUserInfoAndSubcriptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
+24
-12
@@ -51,7 +51,8 @@ export function GlobalFileDropHandler() {
|
||||
|
||||
const setup = async () => {
|
||||
try {
|
||||
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||
const { getCurrentWebviewWindow } =
|
||||
await import("@tauri-apps/api/webviewWindow");
|
||||
const { convertFileSrc } = await import("@tauri-apps/api/core");
|
||||
const appWindow = getCurrentWebviewWindow();
|
||||
|
||||
@@ -73,7 +74,9 @@ export function GlobalFileDropHandler() {
|
||||
// Find first path with a recognised extension
|
||||
let modalKind: ModalType = null;
|
||||
for (const p of payload.paths) {
|
||||
modalKind = getModalTypeForFileName(p.split(/[/\\]/).pop() ?? "");
|
||||
modalKind = getModalTypeForFileName(
|
||||
p.split(/[/\\]/).pop() ?? "",
|
||||
);
|
||||
if (modalKind) break;
|
||||
}
|
||||
if (!modalKind) return;
|
||||
@@ -88,7 +91,7 @@ export function GlobalFileDropHandler() {
|
||||
if (skippedCount > 0) {
|
||||
toast(
|
||||
`${skippedCount} file${skippedCount > 1 ? "s" : ""} skipped — unsupported or mixed types`,
|
||||
{ icon: "⚠️" }
|
||||
{ icon: "⚠️" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,10 +101,11 @@ export function GlobalFileDropHandler() {
|
||||
const fileName = path.split(/[/\\]/).pop() ?? "file";
|
||||
const assetUrl = convertFileSrc(path);
|
||||
const response = await fetch(assetUrl);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
if (!response.ok)
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
const blob = await response.blob();
|
||||
return new File([blob], fileName);
|
||||
})
|
||||
}),
|
||||
);
|
||||
await appWindow.setFocus();
|
||||
setModalType(modalKind);
|
||||
@@ -121,8 +125,9 @@ export function GlobalFileDropHandler() {
|
||||
};
|
||||
|
||||
setup();
|
||||
return () => { unlisteners.forEach((fn) => fn()); };
|
||||
|
||||
return () => {
|
||||
unlisteners.forEach((fn) => fn());
|
||||
};
|
||||
} else {
|
||||
// HTML5 path (browser dev mode)
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
@@ -135,7 +140,10 @@ export function GlobalFileDropHandler() {
|
||||
const handleDragLeave = (e: DragEvent) => {
|
||||
if (!e.dataTransfer?.types.includes("Files")) return;
|
||||
dragCounter.current--;
|
||||
if (dragCounter.current <= 0) { dragCounter.current = 0; setIsDragging(false); }
|
||||
if (dragCounter.current <= 0) {
|
||||
dragCounter.current = 0;
|
||||
setIsDragging(false);
|
||||
}
|
||||
};
|
||||
const handleDragOver = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -161,14 +169,14 @@ export function GlobalFileDropHandler() {
|
||||
|
||||
// Keep only files matching the detected modal type
|
||||
const matchingFiles = allFiles.filter(
|
||||
(f) => getModalTypeForFileName(f.name) === modalKind
|
||||
(f) => getModalTypeForFileName(f.name) === modalKind,
|
||||
);
|
||||
|
||||
const skippedCount = allFiles.length - matchingFiles.length;
|
||||
if (skippedCount > 0) {
|
||||
toast(
|
||||
`${skippedCount} file${skippedCount > 1 ? "s" : ""} skipped — unsupported or mixed types`,
|
||||
{ icon: "⚠️" }
|
||||
{ icon: "⚠️" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,8 +202,12 @@ export function GlobalFileDropHandler() {
|
||||
<div className="pointer-events-none fixed inset-0 z-[9999] flex items-center justify-center bg-black/70">
|
||||
<div className="flex flex-col items-center gap-3 border border-dashed border-white/60 bg-ui-panel px-16 py-12 text-white">
|
||||
<Maximize2Icon className="text-3xl opacity-60" />
|
||||
<div className="font-display text-2xl tracking-tight">Drop to Upload</div>
|
||||
<div className="hud-label text-white/50">GLB, PNG, JPG, JPEG, SPZ</div>
|
||||
<div className="font-display text-2xl tracking-tight">
|
||||
Drop to Upload
|
||||
</div>
|
||||
<div className="hud-label text-white/50">
|
||||
GLB, PNG, JPG, JPEG, SPZ
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -31,7 +31,10 @@ import {
|
||||
useSelectedModel,
|
||||
useSelectedProviderForModel,
|
||||
} from "@storyteller/ui-model-selector";
|
||||
import { CostCalculatorButton, useCostBreakdownModalStore } from "@storyteller/ui-pricing-modal";
|
||||
import {
|
||||
CostCalculatorButton,
|
||||
useCostBreakdownModalStore,
|
||||
} from "@storyteller/ui-pricing-modal";
|
||||
import { HelpMenuButton } from "@storyteller/ui-help-menu";
|
||||
import { CreateEmptyState } from "~/components/generation-feed/CreateEmptyState";
|
||||
|
||||
@@ -88,7 +91,8 @@ export const ImageTo3DExperience = ({
|
||||
);
|
||||
const selectedObjectModel = useSelectedModel(OBJECT_MODEL_PAGE);
|
||||
const selectedObjectModelId =
|
||||
(selectedObjectModel?.id as EnqueueImageTo3dObjectModel | undefined) ?? DEFAULT_OBJECT_MODEL_ID;
|
||||
(selectedObjectModel?.id as EnqueueImageTo3dObjectModel | undefined) ??
|
||||
DEFAULT_OBJECT_MODEL_ID;
|
||||
const worldCredits = useCostBreakdownModalStore(
|
||||
(s) => s.estimatedCreditsByPage[WORLD_MODEL_PAGE],
|
||||
);
|
||||
@@ -112,7 +116,9 @@ export const ImageTo3DExperience = ({
|
||||
const [selectedResultId, setSelectedResultId] = useState<string | null>(null);
|
||||
const promptContentRef = useRef<HTMLDivElement>(null);
|
||||
const [promptHeight, setPromptHeight] = useState<number>(400);
|
||||
const [vh, setVh] = useState<number>(typeof window !== "undefined" ? window.innerHeight : 800);
|
||||
const [vh, setVh] = useState<number>(
|
||||
typeof window !== "undefined" ? window.innerHeight : 800,
|
||||
);
|
||||
|
||||
const objectResults = useImageTo3DStore((s) => s.results);
|
||||
const objectStartGeneration = useImageTo3DStore((s) => s.startGeneration);
|
||||
@@ -123,16 +129,26 @@ export const ImageTo3DExperience = ({
|
||||
const worldStartGeneration = useImageTo3DWorldStore((s) => s.startGeneration);
|
||||
const worldFailGeneration = useImageTo3DWorldStore((s) => s.failGeneration);
|
||||
const worldReset = useImageTo3DWorldStore((s) => s.reset);
|
||||
const pendingExternalImage = useImageTo3DWorldStore((s) => s.pendingExternalImage);
|
||||
const clearPendingExternalImage = useImageTo3DWorldStore((s) => s.clearPendingExternalImage);
|
||||
const pendingExternalImage = useImageTo3DWorldStore(
|
||||
(s) => s.pendingExternalImage,
|
||||
);
|
||||
const clearPendingExternalImage = useImageTo3DWorldStore(
|
||||
(s) => s.clearPendingExternalImage,
|
||||
);
|
||||
const pendingObjectImage = useImageTo3DStore((s) => s.pendingExternalImage);
|
||||
const clearPendingObjectImage = useImageTo3DStore((s) => s.clearPendingExternalImage);
|
||||
const clearPendingObjectImage = useImageTo3DStore(
|
||||
(s) => s.clearPendingExternalImage,
|
||||
);
|
||||
|
||||
const results = variant === "object" ? objectResults : worldResults;
|
||||
const resetResults = variant === "object" ? objectReset : worldReset;
|
||||
const [uploadedMediaToken, setUploadedMediaToken] = useState<string | null>(null);
|
||||
const [uploadedMediaToken, setUploadedMediaToken] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>([]);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Read from store directly to avoid Strict Mode double-execution adding the image twice
|
||||
@@ -256,7 +272,9 @@ export const ImageTo3DExperience = ({
|
||||
|
||||
const handleWorldFiles = async (files: File[]) => {
|
||||
const remaining = MAX_WORLD_IMAGES - worldImages.length;
|
||||
const imageFiles = files.filter((f) => f.type.startsWith("image/")).slice(0, remaining);
|
||||
const imageFiles = files
|
||||
.filter((f) => f.type.startsWith("image/"))
|
||||
.slice(0, remaining);
|
||||
|
||||
await Promise.all(
|
||||
imageFiles.map(async (file) => {
|
||||
@@ -380,7 +398,8 @@ export const ImageTo3DExperience = ({
|
||||
.filter((img) => img.mediaToken && !img.isUploading)
|
||||
.map((img) => img.mediaToken!);
|
||||
const note =
|
||||
worldPrompt.trim() || `${worldImages.length} image${worldImages.length !== 1 ? "s" : ""}`;
|
||||
worldPrompt.trim() ||
|
||||
`${worldImages.length} image${worldImages.length !== 1 ? "s" : ""}`;
|
||||
const firstPreview = worldImages[0]?.preview;
|
||||
|
||||
worldStartGeneration("image", note, firstPreview, subscriberId);
|
||||
@@ -388,7 +407,9 @@ export const ImageTo3DExperience = ({
|
||||
|
||||
window.__storeTaskEnqueueMeta?.({
|
||||
prompt: worldPrompt.trim() || undefined,
|
||||
refImageUrls: worldImages.map((img) => img.preview).filter(Boolean) as string[],
|
||||
refImageUrls: worldImages
|
||||
.map((img) => img.preview)
|
||||
.filter(Boolean) as string[],
|
||||
modelType:
|
||||
(selectedWorldModel as any)?.tauriId ||
|
||||
(SPLAT_MODELS[0] as any)?.tauriId ||
|
||||
@@ -408,9 +429,18 @@ export const ImageTo3DExperience = ({
|
||||
const snapshotPrompt = prompt.trim();
|
||||
const snapshotPreview = uploadedPreview || undefined;
|
||||
const snapshotName = uploadedName;
|
||||
const note = activeMode === "text" ? snapshotPrompt : snapshotName || "Generated Model";
|
||||
const note =
|
||||
activeMode === "text"
|
||||
? snapshotPrompt
|
||||
: snapshotName || "Generated Model";
|
||||
|
||||
objectStartGeneration(activeMode, note, snapshotPreview, false, subscriberId);
|
||||
objectStartGeneration(
|
||||
activeMode,
|
||||
note,
|
||||
snapshotPreview,
|
||||
false,
|
||||
subscriberId,
|
||||
);
|
||||
setSelectedResultId(subscriberId);
|
||||
|
||||
window.__storeTaskEnqueueMeta?.({
|
||||
@@ -423,7 +453,9 @@ export const ImageTo3DExperience = ({
|
||||
await GenerateMesh({
|
||||
prompt: snapshotPrompt || undefined,
|
||||
reference_image_media_tokens:
|
||||
activeMode === "image" && uploadedMediaToken ? [uploadedMediaToken] : undefined,
|
||||
activeMode === "image" && uploadedMediaToken
|
||||
? [uploadedMediaToken]
|
||||
: undefined,
|
||||
model: selectedObjectModel?.tauriId ?? selectedObjectModelId,
|
||||
frontend_caller: "mini_app",
|
||||
frontend_subscriber_id: subscriberId,
|
||||
@@ -434,7 +466,10 @@ export const ImageTo3DExperience = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = commandErrorMessage(error, "An unexpected error occurred");
|
||||
const errorMessage = commandErrorMessage(
|
||||
error,
|
||||
"An unexpected error occurred",
|
||||
);
|
||||
toast.error(`Failed to generate 3D model: ${errorMessage}`);
|
||||
if (variant === "world") {
|
||||
worldFailGeneration(subscriberId);
|
||||
@@ -459,7 +494,15 @@ export const ImageTo3DExperience = ({
|
||||
return prompt.trim().length > 3;
|
||||
}
|
||||
return true;
|
||||
}, [variant, activeMode, uploadedMediaToken, prompt, isGenerating, isUploading, worldImages]);
|
||||
}, [
|
||||
variant,
|
||||
activeMode,
|
||||
uploadedMediaToken,
|
||||
prompt,
|
||||
isGenerating,
|
||||
isUploading,
|
||||
worldImages,
|
||||
]);
|
||||
|
||||
const hasResults = results.length > 0;
|
||||
const showPromptAtBottom = hasResults;
|
||||
@@ -471,7 +514,9 @@ export const ImageTo3DExperience = ({
|
||||
const centerTop = vh / 2 - promptHeight / 2 + 80;
|
||||
const bottomTop = vh - bottomOffsetPx;
|
||||
|
||||
const targetTop = showPromptAtBottom ? Math.max(0, bottomTop) : Math.max(0, centerTop);
|
||||
const targetTop = showPromptAtBottom
|
||||
? Math.max(0, bottomTop)
|
||||
: Math.max(0, centerTop);
|
||||
|
||||
const promptAnim = useSpring({
|
||||
top: targetTop,
|
||||
@@ -539,7 +584,10 @@ export const ImageTo3DExperience = ({
|
||||
}}
|
||||
>
|
||||
<PlusIcon
|
||||
className={twMerge("text-base-fg opacity-80", hasResults ? "text-2xl" : "text-4xl")}
|
||||
className={twMerge(
|
||||
"text-base-fg opacity-80",
|
||||
hasResults ? "text-2xl" : "text-4xl",
|
||||
)}
|
||||
/>
|
||||
{!hasResults && (
|
||||
<span className="mt-3 font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-base-fg/60">
|
||||
@@ -663,7 +711,8 @@ export const ImageTo3DExperience = ({
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node)) setDragActive(false);
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node))
|
||||
setDragActive(false);
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -671,7 +720,9 @@ export const ImageTo3DExperience = ({
|
||||
setDragActive(false);
|
||||
if (e.dataTransfer?.files)
|
||||
handleWorldFiles(
|
||||
Array.from(e.dataTransfer.files).filter((f) => f.type.startsWith("image/")),
|
||||
Array.from(e.dataTransfer.files).filter((f) =>
|
||||
f.type.startsWith("image/"),
|
||||
),
|
||||
);
|
||||
}}
|
||||
onClick={() => worldFileInputRef.current?.click()}
|
||||
@@ -717,7 +768,10 @@ export const ImageTo3DExperience = ({
|
||||
<img
|
||||
src={img.preview}
|
||||
alt={img.name}
|
||||
className={twMerge("h-full w-full object-cover", img.isUploading && "opacity-50 blur-sm")}
|
||||
className={twMerge(
|
||||
"h-full w-full object-cover",
|
||||
img.isUploading && "opacity-50 blur-sm",
|
||||
)}
|
||||
/>
|
||||
{img.isUploading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
@@ -775,7 +829,10 @@ export const ImageTo3DExperience = ({
|
||||
onClick={() => worldFileInputRef.current?.click()}
|
||||
>
|
||||
<PlusIcon
|
||||
className={twMerge("text-base-fg opacity-80", hasResults ? "text-lg" : "text-2xl")}
|
||||
className={twMerge(
|
||||
"text-base-fg opacity-80",
|
||||
hasResults ? "text-lg" : "text-2xl",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -849,7 +906,8 @@ export const ImageTo3DExperience = ({
|
||||
return renderImageMode();
|
||||
};
|
||||
|
||||
const activeResult = results.find((r) => r.id === selectedResultId) || results[0];
|
||||
const activeResult =
|
||||
results.find((r) => r.id === selectedResultId) || results[0];
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-56px)] w-full bg-ui-background text-base-fg">
|
||||
@@ -874,7 +932,11 @@ export const ImageTo3DExperience = ({
|
||||
<div className="relative z-10 h-full w-full p-8">
|
||||
{!hasResults && (
|
||||
<div className="pointer-events-none absolute left-0 top-[calc(50%-280px)] flex w-full justify-center">
|
||||
<CreateEmptyState title={title} subtitle={subtitle} className="py-8 sm:py-8" />
|
||||
<CreateEmptyState
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
className="py-8 sm:py-8"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -929,11 +991,14 @@ export const ImageTo3DExperience = ({
|
||||
variant="action"
|
||||
icon={BoxIcon}
|
||||
onClick={() => {
|
||||
toast.promise(downloadFileFromUrl(activeResult.modelUrl!), {
|
||||
loading: "Downloading GLB...",
|
||||
success: "Downloaded GLB file",
|
||||
error: "Failed to download file",
|
||||
});
|
||||
toast.promise(
|
||||
downloadFileFromUrl(activeResult.modelUrl!),
|
||||
{
|
||||
loading: "Downloading GLB...",
|
||||
success: "Downloaded GLB file",
|
||||
error: "Failed to download file",
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
GLB
|
||||
@@ -1000,7 +1065,9 @@ export const ImageTo3DExperience = ({
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-1.5 text-xs">
|
||||
{isPending ? (
|
||||
<span className="text-base-fg/55">Generating…</span>
|
||||
<span className="text-base-fg/55">
|
||||
Generating…
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-mono tabular-nums text-base-fg/45">
|
||||
{formatTime(result.timestamp)}
|
||||
@@ -1021,13 +1088,21 @@ export const ImageTo3DExperience = ({
|
||||
<animated.div
|
||||
className={twMerge(
|
||||
"fixed left-1/2 z-20 w-full -translate-x-1/2",
|
||||
variant === "world" ? (hasResults ? "max-w-2xl" : "max-w-lg") : "max-w-md",
|
||||
variant === "world"
|
||||
? hasResults
|
||||
? "max-w-2xl"
|
||||
: "max-w-lg"
|
||||
: "max-w-md",
|
||||
)}
|
||||
style={promptAnim}
|
||||
>
|
||||
<div ref={promptContentRef}>
|
||||
<div className={twMerge("glass w-full", hasResults ? "p-3" : "p-5")}>
|
||||
<div className={hasResults ? "space-y-3" : "space-y-5"}>{renderActiveMode()}</div>
|
||||
<div
|
||||
className={twMerge("glass w-full", hasResults ? "p-3" : "p-5")}
|
||||
>
|
||||
<div className={hasResults ? "space-y-3" : "space-y-5"}>
|
||||
{renderActiveMode()}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -1042,7 +1117,9 @@ export const ImageTo3DExperience = ({
|
||||
? IMAGE_TO_3D_WORLD_PAGE_MODEL_LIST
|
||||
: IMAGE_TO_3D_OBJECT_PAGE_MODEL_LIST
|
||||
}
|
||||
page={variant === "world" ? WORLD_MODEL_PAGE : OBJECT_MODEL_PAGE}
|
||||
page={
|
||||
variant === "world" ? WORLD_MODEL_PAGE : OBJECT_MODEL_PAGE
|
||||
}
|
||||
/>
|
||||
<GenerateButton
|
||||
variant="primary"
|
||||
@@ -1073,7 +1150,9 @@ export const ImageTo3DExperience = ({
|
||||
|
||||
<div className="absolute bottom-4 right-4 z-20 flex items-center gap-2">
|
||||
<CostCalculatorButton
|
||||
modelPage={variant === "world" ? WORLD_MODEL_PAGE : OBJECT_MODEL_PAGE}
|
||||
modelPage={
|
||||
variant === "world" ? WORLD_MODEL_PAGE : OBJECT_MODEL_PAGE
|
||||
}
|
||||
/>
|
||||
<HelpMenuButton />
|
||||
</div>
|
||||
@@ -1109,7 +1188,9 @@ export const ImageTo3DExperience = ({
|
||||
mode="select"
|
||||
selectedItemIds={selectedGalleryImages}
|
||||
onSelectItem={handleImageSelect}
|
||||
maxSelections={variant === "world" ? MAX_WORLD_IMAGES - worldImages.length : 1}
|
||||
maxSelections={
|
||||
variant === "world" ? MAX_WORLD_IMAGES - worldImages.length : 1
|
||||
}
|
||||
onUseSelected={handleGallerySelect}
|
||||
onDownloadClicked={downloadFileFromUrl}
|
||||
forceFilter="image"
|
||||
|
||||
@@ -31,4 +31,3 @@ export const useImageTo3DStore = create<ImageTo3DState>((set) => ({
|
||||
})),
|
||||
reset: () => set({ results: [] }),
|
||||
}));
|
||||
|
||||
Loaded 100 of 3426 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user