Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ members = [
]

[workspace.package]
version = "0.2.81"
version = "0.2.82"
edition = "2024"
license = "MIT"
repository = "https://github.com/profullstack/c0mpute"
Expand Down
2 changes: 1 addition & 1 deletion apps/tui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@c0mpute/tui",
"version": "0.2.81",
"version": "0.2.82",
"private": true,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "c0mpute",
"version": "0.2.81",
"version": "0.2.82",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/releases/latest.json/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const dynamic = "force-dynamic";
* this in sync with the workspace version in `Cargo.toml` on each release so a
* freshly built CLI reports "already latest" rather than a phantom upgrade.
*/
const FALLBACK_VERSION = "0.2.81";
const FALLBACK_VERSION = "0.2.82";
const FALLBACK_MIN_REQUIRED = "0.0.1";

type Channel = "stable" | "beta" | "nightly";
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/lib/chat/music.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ describe("chat music", () => {
expect(typeof parseMusicRequest({ prompt: "x", duration: 5 })).toBe("string");
expect(typeof parseMusicRequest({ prompt: "x", duration: 200 })).toBe("string");
expect(typeof parseMusicRequest({ prompt: "" })).toBe("string");
expect(parseMusicRequest({ prompt: "x".repeat(100_000), lyrics: "y".repeat(100_000) })).toMatchObject({ duration: 30 });
expect(typeof parseMusicRequest({ prompt: "x".repeat(100_001) })).toBe("string");
expect(typeof parseMusicRequest(null)).toBe("string");
});
test("file extensions follow the content type", () => {
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/lib/chat/music.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function parseMusicCommand(text: string): MusicCommand | null {
return prompt ? { prompt, lyrics } : null;
}

export const MAX_TEXT_CHARS = 100_000;
export const MIN_SECONDS = 10;
export const MAX_SECONDS = 120;

Expand All @@ -29,9 +30,10 @@ export function parseMusicRequest(
): { prompt: string; lyrics: string; duration: number } | string {
const b = (body ?? {}) as Record<string, unknown>;
const prompt = typeof b.prompt === "string" ? b.prompt.trim() : "";
if (!prompt || prompt.length > 1000) return "prompt must contain 1 to 1000 characters";
// Long briefs and whole songs are fine; Infernet clips what the model reads.
if (!prompt || prompt.length > MAX_TEXT_CHARS) return `prompt must contain 1 to ${MAX_TEXT_CHARS} characters`;
const lyrics = typeof b.lyrics === "string" ? b.lyrics.trim() : "";
if (lyrics.length > 4000) return "lyrics must be at most 4000 characters";
if (lyrics.length > MAX_TEXT_CHARS) return `lyrics must be at most ${MAX_TEXT_CHARS} characters`;
const duration = b.duration === undefined || b.duration === null ? 30 : Number(b.duration);
if (!Number.isInteger(duration) || duration < MIN_SECONDS || duration > MAX_SECONDS) {
return `duration must be a whole number of seconds from ${MIN_SECONDS} to ${MAX_SECONDS}`;
Expand Down
48 changes: 46 additions & 2 deletions node/crates/c0mpute-core/src/music.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ pub const MODEL_TAG: &str = "c0mpute:music:model:ace-step-1.5";
/// Largest song a worker returns or a buyer accepts. A 2-minute 128 kb/s MP3
/// is ~2 MB; the FLAC fallback for the same song is up to ~20 MB.
pub const MAX_RESULT_BYTES: usize = 32 * 1024 * 1024;
pub const MAX_PROMPT_CHARS: usize = 1_000;
pub const MAX_LYRICS_CHARS: usize = 4_000;
/// What a buyer may send. Longer than the model reads on purpose: callers
/// paste whole briefs and songs, and `clip_for_model` trims them.
pub const MAX_PROMPT_CHARS: usize = 100_000;
pub const MAX_LYRICS_CHARS: usize = 100_000;
/// What reaches the worker: ACE-Step's tokenizer keeps 256 caption tokens
/// (~1000 chars) and 2048 lyric tokens, and the spec rides in a gossiped
/// offer capped at 64 KiB, so the gateway clips to these before auctioning.
pub const MODEL_PROMPT_CHARS: usize = 1_000;
pub const MODEL_LYRICS_CHARS: usize = 4_000;
pub const MIN_SECONDS: u32 = 10;
pub const MAX_SECONDS: u32 = 120;
pub const DEFAULT_SECONDS: u32 = 30;
Expand Down Expand Up @@ -398,7 +405,27 @@ fn default_steps() -> u32 {
8
}

/// Clip to `max` characters, preferring to end at a line break or space.
pub fn clip_text(text: &str, max: usize) -> String {
let text = text.trim();
if text.chars().count() <= max {
return text.to_string();
}
let head: String = text.chars().take(max).collect();
let cut = head.rfind(['\n', ' ']).filter(|&i| i * 5 > head.len() * 4);
cut.map_or(head.as_str(), |i| &head[..i]).trim().to_string()
}

impl Txt2MusicSpec {
/// The same request trimmed to what the model reads (see `MODEL_*_CHARS`).
pub fn clip_for_model(&self) -> Self {
Self {
prompt: clip_text(&self.prompt, MODEL_PROMPT_CHARS),
lyrics: clip_text(&self.lyrics, MODEL_LYRICS_CHARS),
..self.clone()
}
}

/// Shared by the gateway (reject early) and the worker (never trust the buyer).
pub fn validate(&self) -> std::result::Result<(), String> {
let chars = self.prompt.trim().chars().count();
Expand Down Expand Up @@ -676,6 +703,23 @@ mod tests {
assert!(s.validate().is_err());
}

#[test]
fn long_text_is_accepted_and_clipped_for_the_model() {
let long = "word ".repeat(19_999) + "word";
let mut s = spec();
s.prompt = long.clone();
s.lyrics = long;
assert!(s.validate().is_ok());
let c = s.clip_for_model();
assert!(c.prompt.chars().count() <= MODEL_PROMPT_CHARS && c.prompt.chars().count() > 800);
assert!(c.prompt.ends_with("word"));
assert!(c.lyrics.chars().count() <= MODEL_LYRICS_CHARS);
assert!(serde_json::to_vec(&c).unwrap().len() < 32 * 1024, "fits a gossip offer");
assert!(c.validate().is_ok());
assert_eq!(clip_text(" short ", 10), "short");
assert_eq!(clip_text(&"\u{e9}".repeat(5000), 1000).chars().count(), 1000);
}

#[test]
fn audio_types_come_from_magic_bytes() {
let pad = |head: &[u8]| {
Expand Down
7 changes: 5 additions & 2 deletions node/crates/c0mpute-core/src/speech_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ const IMAGE_QUEUE_WAIT: Duration = Duration::from_secs(4 * 60);
/// Songs in flight through one gateway; each holds a whole worker for minutes.
const MAX_CONCURRENT_MUSIC_JOBS: usize = 4;
/// How long to keep re-auctioning a song while every music worker is busy.
const MUSIC_QUEUE_WAIT: Duration = Duration::from_secs(3 * 60);
/// A song holds a worker ~2.5 min on CPU, so this waits behind two or three;
/// Infernet gives the whole call 19 min.
const MUSIC_QUEUE_WAIT: Duration = Duration::from_secs(8 * 60);
const MAX_CONCURRENT_OCR_JOBS: usize = 4;
const MAX_CONCURRENT_EMBED_JOBS: usize = 8;

Expand Down Expand Up @@ -1000,7 +1002,8 @@ async fn generate_music(
.acquire_owned()
.await
.map_err(|_| ApiError(StatusCode::SERVICE_UNAVAILABLE, "music queue is unavailable".into()))?;
let spec = serde_json::to_value(&input).map_err(|error| gateway_failure(error.into()))?;
let spec = serde_json::to_value(input.clip_for_model())
.map_err(|error| gateway_failure(error.into()))?;
let (outcome, bytes) = run_speech_job(
&state,
crate::music::WORKLOAD_TXT2MUSIC,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "c0mpute-monorepo",
"version": "0.2.81",
"version": "0.2.82",
"private": true,
"workspaces": [
"apps/*",
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@c0mpute/shared",
"version": "0.2.81",
"version": "0.2.82",
"private": true,
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion plugins/ad-generator-agent/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
[module]
id = "ad-generator-agent"
name = "Ad Generator Agent — every ad type from one URL"
version = "0.2.81"
version = "0.2.82"
kind = "service"
category = "applications"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/ads-manager/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
[module]
id = "ads-manager"
name = "Ads Manager — shared ad operations layer"
version = "0.2.81"
version = "0.2.82"
kind = "service"
category = "applications"
status = "partial"
Expand Down
2 changes: 1 addition & 1 deletion plugins/coinpay/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[module]
id = "coinpay"
name = "CoinPay — DID, payments, escrow, reputation"
version = "0.2.81"
version = "0.2.82"
kind = "service"
category = "applications"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/crawl/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[module]
id = "crawl"
name = "Crawl — distributed web fetching"
version = "0.2.81"
version = "0.2.82"
kind = "workload"
category = "data"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/ctv-ads/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
[module]
id = "ctv-ads"
name = "CTV Ads — VAST and VMAP from every gateway"
version = "0.2.81"
version = "0.2.82"
kind = "service"
category = "applications"
status = "partial"
Expand Down
2 changes: 1 addition & 1 deletion plugins/diffusion/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[module]
id = "diffusion"
name = "Diffusion — image generation"
version = "0.2.81"
version = "0.2.82"
kind = "workload"
category = "ai"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/embed/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
[module]
id = "embed"
name = "Embeddings — vector generation"
version = "0.2.81"
version = "0.2.82"
kind = "workload"
category = "ai"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/gameserver/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
[module]
id = "gameserver"
name = "Game server — Minecraft / Factorio / Valheim"
version = "0.2.81"
version = "0.2.82"
kind = "workload"
category = "infrastructure"
status = "available"
Expand Down
2 changes: 1 addition & 1 deletion plugins/hosting/module.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
[module]
id = "hosting"
name = "Hosting — p2p static sites"
version = "0.2.81"
version = "0.2.82"
kind = "service"
category = "data"
status = "available"
Expand Down
Loading
Loading