File size: 2,417 Bytes
2151ea1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f77d660
 
 
 
 
 
 
 
 
2151ea1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Image engines backed by our /portrait route (server-side, so keys stay off the client):
//   • zimage-local — Z-Image-Turbo on YOUR GPU (TINY_IMAGE_MODE=local). Localhost only.
//   • flux / flux-dev — FLUX via NVIDIA NIM (or HF Inference) in the cloud.
// Mirrors ttsQwen3.js (engine + engineLocal). generate() returns a PNG Blob.

export const isLocalhost = () => {
  try { return /^(localhost|127\.0\.0\.1|\[?::1\]?|0\.0\.0\.0)$/i.test(location.hostname) } catch { return false }
}

async function postPortrait(body) {
  const resp = await fetch('/portrait', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
  })
  if (!resp.ok) throw new Error(`portrait ${resp.status}: ${(await resp.text()).slice(0, 160)}`)
  return resp.blob() // image/png
}

const common = { needsDownload: false, networked: true, ensure: async () => {} }

// LOCAL: same-origin /portrait on a locally-run app.py (TINY_IMAGE_MODE=local → Z-Image
// on your GPU). Only offered on localhost; shown disabled with a note in prod.
export const engineLocal = {
  ...common,
  id: 'zimage-local',
  label: 'Z-Image-Turbo · local (your GPU)',
  available: () => isLocalhost(),
  note: 'run the project locally',
  generate: (prompt, { seed } = {}) => postPortrait({ prompt, seed, engine: 'local' }),
  backendLabel: () => '🖥 local model',
}

export const engineKleinZeroGpu = {
  ...common,
  id: 'klein-zerogpu',
  label: 'FLUX.2 klein 4B · ZeroGPU',
  available: () => true,
  generate: (prompt, { seed } = {}) => postPortrait({ prompt, seed, engine: 'klein', provider: 'flux-klein-4b' }),
  backendLabel: () => 'ZeroGPU FLUX.2 klein 4B',
}

// CLOUD: FLUX via the backend proxy (NVIDIA NIM, else HF Inference). `provider` picks the
// NIM sub-model. schnell = fast (4 steps), dev = higher quality (28 steps).
export const engineCloud = {
  ...common,
  id: 'flux',
  label: 'FLUX schnell · cloud (fast)',
  available: () => true,
  generate: (prompt, { seed } = {}) => postPortrait({ prompt, seed, engine: 'cloud', provider: 'flux-schnell' }),
  backendLabel: () => '☁ FLUX (NIM)',
}

export const engineCloudDev = {
  ...common,
  id: 'flux-dev',
  label: 'FLUX dev · cloud (higher quality)',
  available: () => true,
  generate: (prompt, { seed } = {}) => postPortrait({ prompt, seed, engine: 'cloud', provider: 'flux-dev' }),
  backendLabel: () => '☁ FLUX dev (NIM)',
}