PageSourceSearch

https://kitme.ai/lookgen.js?v=12

js kitme.ai collected 2026-10-05 13:39:46 UTC 5,140 bytes, 140 lines download raw bytes

1// MyKit — batch-generate photorealistic look images using Replicate Flux Schnell.
2// Requires a Replicate API token (see Settings). Uses the local same-origin
3// proxy when served by server.js so browsers do not hit Replicate CORS limits.
4// Each image is generated, resized to 512×640, and stored as a base64 JPEG in
5// localStorage keyed by look id.
6
7const LookGen = (() => {
8
9  const MODEL = 'black-forest-labs/flux-schnell';
10  const THUMB_W = 512;
11  const THUMB_H = 640;
12
13  async function generateAll({ replicateKey, onProgress, lookIds }) {
14    if (!replicateKey) throw new Error('Replicate API token required');
15
16    const targets = lookIds
17      ? LOOKS.filter(l => lookIds.includes(l.id))
18      : LOOKS;
19
20    let done = 0;
21    const total = targets.length;
22    const errors = [];
23
24    for (const look of targets) {
25      try {
26        onProgress && onProgress({ done, total, currentId: look.id, currentName: look.name });
27        const dataUrl = await generateOne({ look, replicateKey });
28        Store.setLookImage(look.id, dataUrl);
29      } catch (err) {
30        console.error(`Failed generating ${look.id}:`, err);
31        errors.push({ id: look.id, error: err.message || String(err) });
32      }
33      done++;
34      onProgress && onProgress({ done, total, currentId: look.id, currentName: look.name });
35    }
36
37    return { done, total, errors };
38  }
39
40  async function generateOne({ look, replicateKey }) {
41    // Using Prefer: wait tells Replicate to hold the connection synchronously
42    // until the prediction finishes (up to 60s). Flux Schnell is fast enough
43    // that this almost always returns in one request.
44    const res = await fetch(replicateUrl(`/v1/models/${MODEL}/predictions`), {
45      method: 'POST',
46      headers: {
47        'Authorization': `Token ${replicateKey}`,
48        'Content-Type': 'application/json',
49        'Prefer': 'wait',
50      },
51      body: JSON.stringify({
52        input: {
53          prompt: look.prompt,
54          aspect_ratio: '4:5',
55          num_outputs: 1,
56          output_format: 'jpg',
57          output_quality: 85,
58          num_inference_steps: 4,
59          megapixels: '1',
60          disable_safety_checker: false,
61        },
62      }),
63    });
64
65    if (!res.ok) {
66      const text = await res.text();
67      throw new Error(`Replicate ${res.status}: ${text.slice(0, 200)}`);
68    }
69    let pred = await res.json();
70
71    // Fallback to polling if Prefer: wait didn't return a finished prediction
72    if (pred.status && pred.status !== 'succeeded' && pred.status !== 'failed') {
73      const pollPath = pred.urls && pred.urls.get
74        ? new URL(pred.urls.get).pathname
75        : `/v1/predictions/${pred.id}`;
76      const start = Date.now();
77      while (Date.now() - start < 90000) {
78        if (pred.status === 'succeeded') break;
79        if (pred.status === 'failed' || pred.status === 'canceled') {
80          throw new Error('Prediction ' + pred.status + ': ' + (pred.error || ''));
81        }
82        await sleep(1500);
83        const pr = await fetch(replicateUrl(pollPath), {
84          headers: { 'Authorization': `Token ${replicateKey}` },
85        });
86        if (!pr.ok) throw new Error('Poll failed: ' + pr.status);
87        pred = await pr.json();
88      }
89    }
90    if (pred.status !== 'succeeded') throw new Error('Timed out: ' + pred.status);
91
92    const outUrl = Array.isArray(pred.output) ? pred.output[0] : pred.output;
93    if (!outUrl) throw new Error('No output URL');
94
95    return await fetchAndResize(outUrl);
96  }
97
98  // Fetch the generated image and downscale it so we can fit all looks in
99  // localStorage (~50KB each at 512×640 JPEG q72).
100  async function fetchAndResize(url) {
101    const res = await fetch(url);
102    if (!res.ok) throw new Error('CDN fetch ' + res.status);
103    const blob = await res.blob();
104    const bmp = await createImageBitmap(blob);
105    const canvas = document.createElement('canvas');
106    canvas.width = THUMB_W;
107    canvas.height = THUMB_H;
108    const ctx = canvas.getContext('2d');
109    const iR = bmp.width / bmp.height, bR = THUMB_W / THUMB_H;
110    let sx = 0, sy = 0, sw = bmp.width, sh = bmp.height;
111    if (iR > bR) { sw = bmp.height * bR; sx = (bmp.width - sw) / 2; }
112    else { sh = bmp.width / bR; sy = (bmp.height - sh) / 2; }
113    ctx.drawImage(bmp, sx, sy, sw, sh, 0, 0, THUMB_W, THUMB_H);
114    return canvas.toDataURL('image/jpeg', 0.72);
115  }
116
117  function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
118
119  function replicateUrl(pathOrUrl) {
120    const path = /^https:\/\/api\.replicate\.com\//i.test(pathOrUrl)
121      ? new URL(pathOrUrl).pathname + new URL(pathOrUrl).search
122      : pathOrUrl;
123    if (window.location.protocol === 'file:') return `https://api.replicate.com${path}`;
124    return `/api/replicate${path}`;
125  }
126
127  function missingLooks() {
128    const cached = Store.getLookImages();
129    return LOOKS.filter(l => !cached[l.id]).map(l => l.id);
130  }
131
132  function stats() {
133    const cached = Store.getLookImages();
134    const total = LOOKS.length;
135    const done = LOOKS.filter(l => cached[l.id]).length;
136    return { done, total, missing: total - done };
137  }
138
139  return { generateAll, missingLooks, stats };
140})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.