@mevdragon/vidfarm-devcli 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/vidfarm-media/SKILL.md +43 -6
- package/SKILL.director.md +37 -23
- package/SKILL.platform.md +6 -6
- package/demo/dist/app.js +69 -69
- package/demo/dist/favicon.ico +0 -0
- package/dist/src/app.js +1832 -213
- package/dist/src/cli.js +238 -17
- package/dist/src/devcli/clip-store.js +29 -2
- package/dist/src/devcli/clips.js +116 -73
- package/dist/src/devcli/composition-edit.js +262 -0
- package/dist/src/devcli/timeline-edit.js +283 -0
- package/dist/src/editor-chat.js +29 -6
- package/dist/src/frontend/discover-client.js +130 -0
- package/dist/src/frontend/discover-store.js +23 -0
- package/dist/src/frontend/file-directory.js +744 -0
- package/dist/src/frontend/template-editor-chat.js +22 -19
- package/dist/src/landing-page.js +24 -7
- package/dist/src/page-shell.js +25 -1
- package/dist/src/reskin/agency-page.js +214 -170
- package/dist/src/reskin/calendar-page.js +503 -187
- package/dist/src/reskin/chat-page.js +739 -299
- package/dist/src/reskin/discover-page.js +1450 -279
- package/dist/src/reskin/document.js +1392 -16
- package/dist/src/reskin/help-page.js +139 -100
- package/dist/src/reskin/index-page.js +1 -1
- package/dist/src/reskin/inpaint-page.js +547 -0
- package/dist/src/reskin/job-runs-page.js +355 -127
- package/dist/src/reskin/library-page.js +1188 -317
- package/dist/src/reskin/login-page.js +124 -87
- package/dist/src/reskin/pricing-page.js +197 -166
- package/dist/src/reskin/settings-page.js +566 -187
- package/dist/src/reskin/theme.js +434 -17
- package/dist/src/services/clip-curation/gemini.js +5 -0
- package/dist/src/services/clip-curation/hunt.js +79 -1
- package/dist/src/services/clip-curation/index.js +2 -1
- package/dist/src/services/clip-curation/local-agent.js +4 -3
- package/dist/src/services/clip-curation/media-select.js +85 -0
- package/dist/src/services/clip-curation/query.js +5 -1
- package/dist/src/services/clip-curation/refine.js +50 -20
- package/dist/src/services/clip-curation/scan.js +10 -3
- package/dist/src/services/clip-curation/taxonomy.js +3 -1
- package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
- package/dist/src/services/clip-records.js +14 -1
- package/dist/src/services/clip-search.js +43 -13
- package/dist/src/services/file-directory.js +114 -0
- package/dist/src/services/storage.js +24 -1
- package/dist/src/services/upstream.js +5 -5
- package/dist/src/template-editor-shell.js +16 -2
- package/package.json +1 -1
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/file-directory-app.js +2 -0
- package/public/assets/homepage-client-app.js +12 -12
- package/public/assets/page-runtime-client-app.js +24 -24
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
// /reskin/inpaint — "Create Image" masked image editor, ported into the
|
|
2
|
+
// farmville design system. A clean 3-column workbench (upload + prompt | canvas
|
|
3
|
+
// brush mask | result + history) wired to the SAME real endpoints the live
|
|
4
|
+
// /inpaint tool uses:
|
|
5
|
+
// • temp-file upload → POST /api/v1/user/me/temporary-files/presign
|
|
6
|
+
// → (server|direct) upload → POST /api/v1/user/me/temporary-files
|
|
7
|
+
// • provider keys → GET /api/v1/user/me/provider-keys
|
|
8
|
+
// • create job → POST /api/v1/primitives/images/inpaint
|
|
9
|
+
// body { tracer, payload:{ source_image_url, mask_url,
|
|
10
|
+
// instruction, reference_attachments, provider?, size,
|
|
11
|
+
// aspect_ratio?, output_format } }
|
|
12
|
+
// • poll job → GET /api/v1/primitives/image_inpaint/jobs/:jobId
|
|
13
|
+
//
|
|
14
|
+
// The masking + upload + poll logic is the proven live logic (single-mask
|
|
15
|
+
// variant): the mask PNG is white with alpha holes where the user brushed
|
|
16
|
+
// (destination-out), and the API treats transparent pixels as the only editable
|
|
17
|
+
// region. Everything is same-origin with the session cookie. Loading / auth /
|
|
18
|
+
// error states degrade gracefully; the design stays rk-* farmville.
|
|
19
|
+
import { reskinDocument } from "./document.js";
|
|
20
|
+
// Functional SVG glyphs (no decorative emoji).
|
|
21
|
+
const UPLOAD_ICON = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 16V4"/><path d="m7 9 5-5 5 5"/><path d="M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>`;
|
|
22
|
+
const UNDO_ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-1"/></svg>`;
|
|
23
|
+
const CLEAR_ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M18 6 6 18"/></svg>`;
|
|
24
|
+
const SPARK_ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.5l1.9 5.1 5.1 1.9-5.1 1.9L12 16.5l-1.9-5.1L5 9.5l5.1-1.9z"/></svg>`;
|
|
25
|
+
const DL_ICON = `<svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v9"/><path d="m6.5 8.5 3.5 3.5 3.5-3.5"/><path d="M4 16h12"/></svg>`;
|
|
26
|
+
export function renderReskinInpaint(input = {}) {
|
|
27
|
+
const boot = { accountId: input.accountId || "", isLoggedIn: Boolean(input.isLoggedIn) };
|
|
28
|
+
const body = `
|
|
29
|
+
<main class="rk-container-wide rk-page rk-inpaint-page">
|
|
30
|
+
<div class="rk-page-head">
|
|
31
|
+
<h1 class="rk-h1">Create Image</h1>
|
|
32
|
+
<p class="rk-sub">Upload an image, brush a mask over what you want to change, describe the edit, and generate — everything outside the mask stays untouched.</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="rk-inpaint-workbench">
|
|
36
|
+
<!-- LEFT: upload + prompt + brush controls -->
|
|
37
|
+
<section class="rk-inpaint-panel" aria-label="Controls">
|
|
38
|
+
<div class="rk-inpaint-block">
|
|
39
|
+
<label class="rk-label" for="rk-ip-file">Source image</label>
|
|
40
|
+
<label class="rk-inpaint-drop" id="rk-ip-drop" for="rk-ip-file">
|
|
41
|
+
<span class="rk-inpaint-drop-ic" aria-hidden="true">${UPLOAD_ICON}</span>
|
|
42
|
+
<span class="rk-inpaint-drop-main"><strong>Choose an image</strong> or drop it here</span>
|
|
43
|
+
<span class="rk-inpaint-drop-name" id="rk-ip-srcname">No image selected</span>
|
|
44
|
+
<input id="rk-ip-file" class="rk-inpaint-file" type="file" accept="image/*" />
|
|
45
|
+
</label>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="rk-inpaint-block">
|
|
49
|
+
<label class="rk-label" for="rk-ip-prompt">What should change in the masked area?</label>
|
|
50
|
+
<textarea id="rk-ip-prompt" class="rk-textarea rk-inpaint-prompt" placeholder="e.g. replace the sky with a warm golden sunset"></textarea>
|
|
51
|
+
<span class="rk-hint">Only the pixels you brush are edited. The rest of the image is preserved.</span>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="rk-inpaint-block">
|
|
55
|
+
<div class="rk-inpaint-brushrow">
|
|
56
|
+
<label class="rk-label" for="rk-ip-brush">Brush size</label>
|
|
57
|
+
<span class="rk-inpaint-brushval" id="rk-ip-brushval">40 px</span>
|
|
58
|
+
</div>
|
|
59
|
+
<input id="rk-ip-brush" class="rk-inpaint-range" type="range" min="8" max="160" value="40" step="2" />
|
|
60
|
+
<div class="rk-inpaint-tools">
|
|
61
|
+
<button type="button" class="rk-btn rk-btn-ghost rk-btn-sm" id="rk-ip-undo">${UNDO_ICON} Undo</button>
|
|
62
|
+
<button type="button" class="rk-btn rk-btn-ghost rk-btn-sm" id="rk-ip-clear">${CLEAR_ICON} Clear mask</button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="rk-inpaint-block rk-inpaint-provwrap" id="rk-ip-provwrap" data-visible="false">
|
|
67
|
+
<label class="rk-label" for="rk-ip-provider">AI provider</label>
|
|
68
|
+
<select id="rk-ip-provider" class="rk-select rk-inpaint-select"><option value="">Auto</option></select>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="rk-inpaint-keynote" id="rk-ip-keynote" hidden>
|
|
72
|
+
Add an AI provider key (OpenAI, Gemini, or OpenRouter) to generate edits.
|
|
73
|
+
<a href="/settings/developer">Open Settings</a>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<button type="button" class="rk-btn rk-btn-gold rk-inpaint-generate" id="rk-ip-generate" disabled>${SPARK_ICON} Generate edit</button>
|
|
77
|
+
<p class="rk-inpaint-status" id="rk-ip-status" data-tone="" aria-live="polite"></p>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<!-- CENTER: canvas stage -->
|
|
81
|
+
<section class="rk-inpaint-stage" aria-label="Canvas">
|
|
82
|
+
<div class="rk-inpaint-empty" id="rk-ip-empty">
|
|
83
|
+
<span class="rk-inpaint-empty-ic" aria-hidden="true">${UPLOAD_ICON}</span>
|
|
84
|
+
<p class="rk-inpaint-empty-title">Upload an image to start</p>
|
|
85
|
+
<p class="rk-inpaint-empty-sub">Then brush over the area you want the AI to change.</p>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="rk-inpaint-canvaswrap" id="rk-ip-canvaswrap" hidden>
|
|
88
|
+
<img id="rk-ip-img" class="rk-inpaint-img" alt="Source image" />
|
|
89
|
+
<canvas id="rk-ip-canvas" class="rk-inpaint-canvas"></canvas>
|
|
90
|
+
<span class="rk-inpaint-cursor" id="rk-ip-cursor" hidden></span>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<!-- RIGHT: result + history -->
|
|
95
|
+
<section class="rk-inpaint-output" aria-label="Result">
|
|
96
|
+
<div class="rk-inpaint-result" id="rk-ip-result" hidden>
|
|
97
|
+
<div class="rk-inpaint-result-head">
|
|
98
|
+
<h2 class="rk-inpaint-h2">Result</h2>
|
|
99
|
+
<a class="rk-btn rk-btn-ink rk-btn-sm rk-inpaint-dl" id="rk-ip-dl" href="#" download>${DL_ICON} Download</a>
|
|
100
|
+
</div>
|
|
101
|
+
<a class="rk-inpaint-result-imgwrap" id="rk-ip-resultlink" href="#" target="_blank" rel="noopener">
|
|
102
|
+
<img id="rk-ip-resultimg" class="rk-inpaint-result-img" alt="Edited image" />
|
|
103
|
+
</a>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="rk-inpaint-history">
|
|
106
|
+
<h2 class="rk-inpaint-h2">History</h2>
|
|
107
|
+
<div class="rk-inpaint-historylist" id="rk-ip-history"></div>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<script type="application/json" data-rk-inpaint-boot>${JSON.stringify(boot).replace(/</g, "\\u003c")}</script>
|
|
113
|
+
</main>`;
|
|
114
|
+
const pageCss = `
|
|
115
|
+
.rk-inpaint-page{padding-left:var(--rk-gutter);padding-right:var(--rk-gutter)}
|
|
116
|
+
/* slimmer side rails so the center canvas (the primary work surface) isn't
|
|
117
|
+
starved once the 248px app sidebar is subtracted; drop the right rail under
|
|
118
|
+
the canvas sooner (≤1200) so mid-size laptops give the canvas full width. */
|
|
119
|
+
.rk-inpaint-workbench{display:grid;grid-template-columns:280px minmax(0,1fr) 300px;gap:20px;align-items:start}
|
|
120
|
+
@media(max-width:1200px){.rk-inpaint-workbench{grid-template-columns:280px minmax(0,1fr)}.rk-inpaint-output{grid-column:1 / -1;grid-row:2}}
|
|
121
|
+
@media(max-width:820px){.rk-inpaint-workbench{grid-template-columns:1fr}.rk-inpaint-output,.rk-inpaint-stage,.rk-inpaint-panel{grid-column:auto}
|
|
122
|
+
/* stack canvas ABOVE the controls so you can paint without scrolling past Generate */
|
|
123
|
+
.rk-inpaint-stage{order:-1}}
|
|
124
|
+
@media(max-height:520px){.rk-inpaint-stage{min-height:0}}
|
|
125
|
+
|
|
126
|
+
/* panels (no side borders — uniform hairline) */
|
|
127
|
+
.rk-inpaint-panel,.rk-inpaint-stage,.rk-inpaint-output{background:var(--rk-surface);border:1px solid var(--rk-border);border-radius:var(--rk-r-3xl);box-shadow:var(--rk-shadow-card)}
|
|
128
|
+
.rk-inpaint-panel{padding:20px;display:grid;gap:18px;align-content:start}
|
|
129
|
+
.rk-inpaint-block{display:grid;gap:9px}
|
|
130
|
+
.rk-inpaint-h2{font-family:var(--rk-font-display);font-size:var(--rk-text-lg);font-weight:800;color:var(--rk-ink);letter-spacing:-.01em}
|
|
131
|
+
|
|
132
|
+
/* upload dropzone */
|
|
133
|
+
.rk-inpaint-drop{display:grid;justify-items:center;gap:6px;text-align:center;padding:22px 16px;cursor:pointer;
|
|
134
|
+
border:1.5px dashed var(--rk-border-strong);border-radius:var(--rk-r-2xl);background:var(--rk-n-50);color:var(--rk-text-muted);
|
|
135
|
+
transition:border-color var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease)}
|
|
136
|
+
.rk-inpaint-drop:hover,.rk-inpaint-drop.is-drag{border-color:var(--rk-gold-600);background:var(--rk-gold-tint)}
|
|
137
|
+
.rk-inpaint-drop-ic{color:var(--rk-gold-700);display:grid;place-items:center}
|
|
138
|
+
.rk-inpaint-drop-main{font-size:13px}
|
|
139
|
+
.rk-inpaint-drop-main strong{color:var(--rk-ink);font-weight:700}
|
|
140
|
+
.rk-inpaint-drop-name{font-size:12px;color:var(--rk-text-faint);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
141
|
+
.rk-inpaint-file{display:none}
|
|
142
|
+
.rk-inpaint-prompt{min-height:88px}
|
|
143
|
+
|
|
144
|
+
/* brush controls */
|
|
145
|
+
.rk-inpaint-brushrow{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
|
146
|
+
.rk-inpaint-brushval{font-size:12.5px;font-weight:700;color:var(--rk-n-600);font-variant-numeric:tabular-nums}
|
|
147
|
+
.rk-inpaint-range{width:100%;accent-color:var(--rk-gold-700);cursor:pointer}
|
|
148
|
+
.rk-inpaint-tools{display:flex;gap:8px}
|
|
149
|
+
.rk-inpaint-tools .rk-btn{flex:1;gap:6px}
|
|
150
|
+
.rk-inpaint-provwrap[data-visible="false"]{display:none}
|
|
151
|
+
.rk-inpaint-keynote{font-size:12.5px;color:var(--rk-text-muted);line-height:1.5;background:var(--rk-gold-tint);border:1px solid var(--rk-gold-600);border-radius:var(--rk-r-xl);padding:10px 12px}
|
|
152
|
+
.rk-inpaint-keynote[hidden]{display:none}
|
|
153
|
+
.rk-inpaint-keynote a{color:var(--rk-gold-700);font-weight:700}
|
|
154
|
+
.rk-inpaint-generate{width:100%;justify-content:center;gap:8px;padding:13px}
|
|
155
|
+
.rk-inpaint-generate:disabled{opacity:.55;cursor:default}
|
|
156
|
+
.rk-inpaint-status{font-size:12.5px;font-weight:600;line-height:1.5;min-height:18px;color:var(--rk-text-muted)}
|
|
157
|
+
.rk-inpaint-status[data-tone="error"]{color:#b42318}
|
|
158
|
+
.rk-inpaint-status[data-tone="success"]{color:#1f7a44}
|
|
159
|
+
|
|
160
|
+
/* center stage */
|
|
161
|
+
.rk-inpaint-stage{min-height:420px;display:grid;place-items:center;padding:20px;overflow:hidden}
|
|
162
|
+
.rk-inpaint-empty{display:grid;justify-items:center;gap:8px;text-align:center;color:var(--rk-text-muted);padding:40px 20px}
|
|
163
|
+
.rk-inpaint-empty[hidden]{display:none}
|
|
164
|
+
.rk-inpaint-empty-ic{color:var(--rk-n-400)}
|
|
165
|
+
.rk-inpaint-empty-title{font-family:var(--rk-font-display);font-weight:800;font-size:var(--rk-text-lg);color:var(--rk-ink)}
|
|
166
|
+
.rk-inpaint-empty-sub{font-size:13px}
|
|
167
|
+
.rk-inpaint-canvaswrap{position:relative;display:inline-block;line-height:0;max-width:100%;border-radius:var(--rk-r-2xl);overflow:hidden;box-shadow:var(--rk-shadow-md)}
|
|
168
|
+
.rk-inpaint-canvaswrap[hidden]{display:none}
|
|
169
|
+
.rk-inpaint-img{display:block;max-width:100%;max-height:min(64vh,620px);width:auto;height:auto}
|
|
170
|
+
.rk-inpaint-canvas{position:absolute;inset:0;width:100%;height:100%;cursor:crosshair;touch-action:none}
|
|
171
|
+
.rk-inpaint-cursor{position:absolute;z-index:3;border:2px solid rgba(252,185,0,.95);background:rgba(252,185,0,.28);border-radius:50%;
|
|
172
|
+
pointer-events:none;transform:translate(-50%,-50%);box-shadow:0 0 0 1px rgba(0,0,0,.25)}
|
|
173
|
+
.rk-inpaint-cursor[hidden]{display:none}
|
|
174
|
+
|
|
175
|
+
/* right: result + history */
|
|
176
|
+
.rk-inpaint-output{padding:18px;display:grid;gap:18px;align-content:start}
|
|
177
|
+
.rk-inpaint-result[hidden]{display:none}
|
|
178
|
+
.rk-inpaint-result-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
|
|
179
|
+
.rk-inpaint-dl{gap:6px}
|
|
180
|
+
.rk-inpaint-result-imgwrap{display:block;border-radius:var(--rk-r-2xl);overflow:hidden;background:var(--rk-n-100);box-shadow:var(--rk-shadow-sm)}
|
|
181
|
+
.rk-inpaint-result-img{display:block;width:100%;height:auto}
|
|
182
|
+
.rk-inpaint-historylist{display:grid;gap:10px;margin-top:10px}
|
|
183
|
+
.rk-inpaint-hitem{display:flex;align-items:center;gap:12px;padding:8px;border:1px solid var(--rk-border);border-radius:var(--rk-r-xl);background:var(--rk-n-50)}
|
|
184
|
+
.rk-inpaint-hthumb{width:52px;height:52px;flex:none;border-radius:var(--rk-r-lg);overflow:hidden;background:var(--rk-n-200);display:grid;place-items:center}
|
|
185
|
+
.rk-inpaint-hthumb img{width:100%;height:100%;object-fit:cover}
|
|
186
|
+
.rk-inpaint-hspin{width:20px;height:20px;border-radius:50%;border:3px solid var(--rk-gold-tint2);border-top-color:var(--rk-gold-700);animation:rk-ip-spin .8s linear infinite}
|
|
187
|
+
@keyframes rk-ip-spin{to{transform:rotate(360deg)}}
|
|
188
|
+
.rk-inpaint-hmain{flex:1;min-width:0;display:grid;gap:2px}
|
|
189
|
+
.rk-inpaint-htitle{font-size:12.5px;font-weight:700;color:var(--rk-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
190
|
+
.rk-inpaint-hsub{font-size:11.5px;color:var(--rk-text-muted)}
|
|
191
|
+
.rk-inpaint-hdl{flex:none;color:var(--rk-gold-700);display:grid;place-items:center;width:30px;height:30px;border-radius:var(--rk-r-lg);border:1px solid var(--rk-border);background:#fff}
|
|
192
|
+
.rk-inpaint-hdl:hover{background:var(--rk-gold-tint)}
|
|
193
|
+
.rk-inpaint-hempty{font-size:12.5px;color:var(--rk-text-faint);padding:14px 4px;text-align:center}
|
|
194
|
+
`;
|
|
195
|
+
const script = `
|
|
196
|
+
(function(){
|
|
197
|
+
var root=document;
|
|
198
|
+
var boot={};
|
|
199
|
+
try{ var b=root.querySelector('[data-rk-inpaint-boot]'); boot=b?JSON.parse(b.textContent||'{}'):{}; }catch(e){ boot={}; }
|
|
200
|
+
|
|
201
|
+
var fileInput=root.getElementById('rk-ip-file');
|
|
202
|
+
var drop=root.getElementById('rk-ip-drop');
|
|
203
|
+
var srcName=root.getElementById('rk-ip-srcname');
|
|
204
|
+
var promptEl=root.getElementById('rk-ip-prompt');
|
|
205
|
+
var brush=root.getElementById('rk-ip-brush');
|
|
206
|
+
var brushVal=root.getElementById('rk-ip-brushval');
|
|
207
|
+
var undoBtn=root.getElementById('rk-ip-undo');
|
|
208
|
+
var clearBtn=root.getElementById('rk-ip-clear');
|
|
209
|
+
var provWrap=root.getElementById('rk-ip-provwrap');
|
|
210
|
+
var provSelect=root.getElementById('rk-ip-provider');
|
|
211
|
+
var keyNote=root.getElementById('rk-ip-keynote');
|
|
212
|
+
var generate=root.getElementById('rk-ip-generate');
|
|
213
|
+
var statusEl=root.getElementById('rk-ip-status');
|
|
214
|
+
var emptyState=root.getElementById('rk-ip-empty');
|
|
215
|
+
var canvasWrap=root.getElementById('rk-ip-canvaswrap');
|
|
216
|
+
var img=root.getElementById('rk-ip-img');
|
|
217
|
+
var canvas=root.getElementById('rk-ip-canvas');
|
|
218
|
+
var cursor=root.getElementById('rk-ip-cursor');
|
|
219
|
+
var resultBox=root.getElementById('rk-ip-result');
|
|
220
|
+
var resultImg=root.getElementById('rk-ip-resultimg');
|
|
221
|
+
var resultLink=root.getElementById('rk-ip-resultlink');
|
|
222
|
+
var resultDl=root.getElementById('rk-ip-dl');
|
|
223
|
+
var historyList=root.getElementById('rk-ip-history');
|
|
224
|
+
|
|
225
|
+
var MASK_COLOR='rgba(252,185,0,0.55)';
|
|
226
|
+
var sourceUrl='';
|
|
227
|
+
var strokes=[]; // [{size, points:[{x,y}]}]
|
|
228
|
+
var current=null;
|
|
229
|
+
var painting=false;
|
|
230
|
+
var busy=false;
|
|
231
|
+
var historyItems=[]; // [{kind:'pending'|'output', jobId, url, createdAt, status, progress}]
|
|
232
|
+
|
|
233
|
+
function setStatus(msg,tone){ if(!statusEl) return; statusEl.textContent=msg||''; statusEl.setAttribute('data-tone',tone||''); }
|
|
234
|
+
function absUrl(v){ try{ return new URL(v, window.location.href).toString(); }catch(e){ return v; } }
|
|
235
|
+
function parseJson(r){ return r.json().catch(function(){ return {}; }); }
|
|
236
|
+
function tracerId(){ return 'reskin-inpaint-'+Date.now().toString(36)+'-'+Math.floor(Math.random()*1e6).toString(36); }
|
|
237
|
+
|
|
238
|
+
function canGenerate(){ return Boolean(sourceUrl) && strokes.length>0 && !busy; }
|
|
239
|
+
function syncGenerate(){ if(generate) generate.disabled=!canGenerate(); }
|
|
240
|
+
|
|
241
|
+
// ── canvas mask ──────────────────────────────────────────────────────────
|
|
242
|
+
function redraw(){
|
|
243
|
+
var ctx=canvas.getContext('2d'); if(!ctx) return;
|
|
244
|
+
ctx.clearRect(0,0,canvas.width,canvas.height);
|
|
245
|
+
ctx.lineCap='round'; ctx.lineJoin='round'; ctx.globalAlpha=1; ctx.strokeStyle=MASK_COLOR;
|
|
246
|
+
for(var s=0;s<strokes.length;s++){
|
|
247
|
+
var st=strokes[s]; if(!st.points.length) continue;
|
|
248
|
+
ctx.lineWidth=st.size; ctx.beginPath(); ctx.moveTo(st.points[0].x,st.points[0].y);
|
|
249
|
+
if(st.points.length===1){ ctx.lineTo(st.points[0].x+0.01,st.points[0].y+0.01); }
|
|
250
|
+
else { for(var i=1;i<st.points.length;i++){ ctx.lineTo(st.points[i].x,st.points[i].y); } }
|
|
251
|
+
ctx.stroke();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function syncCanvasSize(){
|
|
255
|
+
if(!img.naturalWidth||!img.naturalHeight) return;
|
|
256
|
+
if(canvas.width!==img.naturalWidth||canvas.height!==img.naturalHeight){
|
|
257
|
+
canvas.width=img.naturalWidth; canvas.height=img.naturalHeight; redraw();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function pointOf(e){
|
|
261
|
+
var rect=canvas.getBoundingClientRect();
|
|
262
|
+
return {
|
|
263
|
+
x:Math.max(0,Math.min(canvas.width,(e.clientX-rect.left)*(canvas.width/rect.width))),
|
|
264
|
+
y:Math.max(0,Math.min(canvas.height,(e.clientY-rect.top)*(canvas.height/rect.height)))
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function resetMask(){ strokes=[]; current=null; painting=false; redraw(); syncGenerate(); }
|
|
268
|
+
|
|
269
|
+
canvas.addEventListener('pointerdown',function(e){
|
|
270
|
+
if(!sourceUrl||busy) return;
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
try{ canvas.setPointerCapture(e.pointerId); }catch(_){}
|
|
273
|
+
painting=true;
|
|
274
|
+
current={ size:Number(brush.value)||40, points:[pointOf(e)] };
|
|
275
|
+
strokes.push(current); redraw(); syncGenerate();
|
|
276
|
+
});
|
|
277
|
+
canvas.addEventListener('pointermove',function(e){
|
|
278
|
+
moveCursor(e);
|
|
279
|
+
if(!painting||!current) return;
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
current.points.push(pointOf(e)); redraw();
|
|
282
|
+
});
|
|
283
|
+
function endStroke(e){ if(!painting) return; painting=false; current=null; try{ canvas.releasePointerCapture(e.pointerId); }catch(_){} }
|
|
284
|
+
canvas.addEventListener('pointerup',endStroke);
|
|
285
|
+
canvas.addEventListener('pointercancel',endStroke);
|
|
286
|
+
canvas.addEventListener('pointerleave',function(){ if(cursor) cursor.hidden=true; });
|
|
287
|
+
|
|
288
|
+
// brush cursor preview
|
|
289
|
+
function cursorPx(){
|
|
290
|
+
if(!img.naturalWidth) return Number(brush.value)||40;
|
|
291
|
+
var rect=canvas.getBoundingClientRect();
|
|
292
|
+
return (Number(brush.value)||40)*(rect.width/canvas.width);
|
|
293
|
+
}
|
|
294
|
+
function moveCursor(e){
|
|
295
|
+
if(!cursor||!sourceUrl) return;
|
|
296
|
+
var wrapRect=canvasWrap.getBoundingClientRect();
|
|
297
|
+
var d=cursorPx();
|
|
298
|
+
cursor.hidden=false;
|
|
299
|
+
cursor.style.width=d+'px'; cursor.style.height=d+'px';
|
|
300
|
+
cursor.style.left=(e.clientX-wrapRect.left)+'px';
|
|
301
|
+
cursor.style.top=(e.clientY-wrapRect.top)+'px';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if(brush) brush.addEventListener('input',function(){ if(brushVal) brushVal.innerHTML=(brush.value)+' px'; });
|
|
305
|
+
if(undoBtn) undoBtn.addEventListener('click',function(){ strokes.pop(); redraw(); syncGenerate(); });
|
|
306
|
+
if(clearBtn) clearBtn.addEventListener('click',function(){ resetMask(); });
|
|
307
|
+
|
|
308
|
+
// ── mask blob: white + destination-out holes where painted ────────────────
|
|
309
|
+
function maskBlob(){
|
|
310
|
+
return new Promise(function(resolve,reject){
|
|
311
|
+
var m=document.createElement('canvas'); m.width=canvas.width; m.height=canvas.height;
|
|
312
|
+
var ctx=m.getContext('2d'); if(!ctx){ reject(new Error('Unable to prepare mask image.')); return; }
|
|
313
|
+
ctx.fillStyle='#ffffff'; ctx.fillRect(0,0,m.width,m.height);
|
|
314
|
+
ctx.globalCompositeOperation='destination-out'; ctx.lineCap='round'; ctx.lineJoin='round'; ctx.strokeStyle='rgba(0,0,0,1)';
|
|
315
|
+
for(var s=0;s<strokes.length;s++){
|
|
316
|
+
var st=strokes[s]; if(!st.points.length) continue;
|
|
317
|
+
ctx.lineWidth=st.size; ctx.beginPath(); ctx.moveTo(st.points[0].x,st.points[0].y);
|
|
318
|
+
if(st.points.length===1){ ctx.lineTo(st.points[0].x+0.01,st.points[0].y+0.01); }
|
|
319
|
+
else { for(var i=1;i<st.points.length;i++){ ctx.lineTo(st.points[i].x,st.points[i].y); } }
|
|
320
|
+
ctx.stroke();
|
|
321
|
+
}
|
|
322
|
+
m.toBlob(function(blob){ blob?resolve(blob):reject(new Error('Unable to prepare mask image.')); },'image/png');
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function aspectRatio(){
|
|
326
|
+
var w=canvas.width||img.naturalWidth||0, h=canvas.height||img.naturalHeight||0;
|
|
327
|
+
if(!w||!h) return '';
|
|
328
|
+
var r=w/h;
|
|
329
|
+
if(Math.abs(r-1)<0.08) return '1:1';
|
|
330
|
+
if(r>1) return r>=1.55?'16:9':'4:3';
|
|
331
|
+
return r<=0.65?'9:16':'3:4';
|
|
332
|
+
}
|
|
333
|
+
function buildInstruction(){
|
|
334
|
+
var p=(promptEl.value||'').trim();
|
|
335
|
+
if(!p) return '';
|
|
336
|
+
return [
|
|
337
|
+
'INPAINTING SYSTEM WRAPPER:',
|
|
338
|
+
'You are editing an existing image with an alpha mask. Treat transparent mask pixels as the only editable region. Everything outside the mask must remain the same image: preserve identity, layout, pose, camera angle, framing, background, lighting, colors, textures, text, logos, and all unmasked details.',
|
|
339
|
+
'Do not redraw, reinterpret, beautify, crop, zoom, rotate, expand, or stylize unmasked content. Keep the original dimensions and aspect ratio.',
|
|
340
|
+
'Edit only the masked region to: '+p+'. Match the surrounding perspective, lighting, grain, shadows, and edge blending.'
|
|
341
|
+
].join(' ');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── temp-file upload (presign → upload → finalize) ────────────────────────
|
|
345
|
+
function uploadTemp(blob,fileName,contentType){
|
|
346
|
+
return fetch('/api/v1/user/me/temporary-files/presign',{
|
|
347
|
+
method:'POST',credentials:'same-origin',headers:{'content-type':'application/json'},
|
|
348
|
+
body:JSON.stringify({ file_name:fileName, content_type:contentType, size_bytes:blob.size, folder_path:'inpaint' })
|
|
349
|
+
}).then(parseJson).then(function(pre){
|
|
350
|
+
if(!pre||!pre.transport){ throw new Error((pre&&pre.error)||'Unable to prepare upload.'); }
|
|
351
|
+
if(pre.transport==='server'){
|
|
352
|
+
var fd=new FormData();
|
|
353
|
+
fd.append((pre.upload&&pre.upload.form_field)||'file', new File([blob],fileName,{type:contentType}));
|
|
354
|
+
fd.append('folder_path','inpaint');
|
|
355
|
+
return fetch((pre.upload&&pre.upload.url)||'/api/v1/user/me/temporary-files/upload',{
|
|
356
|
+
method:(pre.upload&&pre.upload.method)||'POST',credentials:'same-origin',body:fd
|
|
357
|
+
}).then(parseJson).then(function(up){
|
|
358
|
+
if(!up||!up.file){ throw new Error((up&&up.error)||'Unable to upload file.'); }
|
|
359
|
+
return up.file.viewUrl||up.file.view_url||up.file.s3Url||up.file.s3_url;
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return fetch(pre.upload.url,{ method:pre.upload.method, headers:pre.upload.headers, body:blob }).then(function(r){
|
|
363
|
+
if(!r.ok) throw new Error('Upload failed with status '+r.status+'.');
|
|
364
|
+
return fetch('/api/v1/user/me/temporary-files',{
|
|
365
|
+
method:'POST',credentials:'same-origin',headers:{'content-type':'application/json'},
|
|
366
|
+
body:JSON.stringify({ file_id:pre.file_id, file_name:pre.file_name, content_type:pre.content_type, size_bytes:blob.size, storage_key:pre.storage_key, folder_path:pre.folder_path||'inpaint' })
|
|
367
|
+
}).then(parseJson).then(function(fin){
|
|
368
|
+
if(!fin||!fin.file){ throw new Error((fin&&fin.error)||'Unable to save file.'); }
|
|
369
|
+
return fin.file.viewUrl||fin.file.view_url||fin.file.s3Url||fin.file.s3_url;
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function loadSourceFile(file){
|
|
376
|
+
if(!file||!/^image\\//.test(file.type||'')){ setStatus('Choose an image file.','error'); return; }
|
|
377
|
+
if(generate) generate.disabled=true;
|
|
378
|
+
setStatus('Uploading source image\\u2026','');
|
|
379
|
+
uploadTemp(file, file.name||'source.png', file.type||'image/png').then(function(url){
|
|
380
|
+
sourceUrl=absUrl(url);
|
|
381
|
+
if(srcName) srcName.textContent=file.name||'Source image';
|
|
382
|
+
if(emptyState) emptyState.hidden=true;
|
|
383
|
+
if(canvasWrap) canvasWrap.hidden=false;
|
|
384
|
+
if(resultBox) resultBox.hidden=true;
|
|
385
|
+
resetMask();
|
|
386
|
+
img.onload=function(){ syncCanvasSize(); resetMask(); };
|
|
387
|
+
img.src=sourceUrl;
|
|
388
|
+
setStatus('Source ready. Brush the area to change.','success');
|
|
389
|
+
}).catch(function(err){
|
|
390
|
+
setStatus((err&&err.message)?err.message:'Unable to upload source image.','error');
|
|
391
|
+
}).then(function(){ syncGenerate(); });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if(fileInput) fileInput.addEventListener('change',function(){ if(fileInput.files&&fileInput.files[0]) loadSourceFile(fileInput.files[0]); });
|
|
395
|
+
if(drop){
|
|
396
|
+
['dragenter','dragover'].forEach(function(ev){ drop.addEventListener(ev,function(e){ e.preventDefault(); drop.classList.add('is-drag'); }); });
|
|
397
|
+
['dragleave','drop'].forEach(function(ev){ drop.addEventListener(ev,function(e){ e.preventDefault(); drop.classList.remove('is-drag'); }); });
|
|
398
|
+
drop.addEventListener('drop',function(e){ var f=e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files[0]; if(f) loadSourceFile(f); });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── provider keys ─────────────────────────────────────────────────────────
|
|
402
|
+
function loadProviders(){
|
|
403
|
+
fetch('/api/v1/user/me/provider-keys',{credentials:'same-origin'}).then(function(r){ return r.ok?parseJson(r):null; }).then(function(j){
|
|
404
|
+
if(!j){ return; }
|
|
405
|
+
var supported={openai:1,gemini:1,openrouter:1}; var seen={}; var providers=[];
|
|
406
|
+
var keys=Array.isArray(j.provider_keys)?j.provider_keys:[];
|
|
407
|
+
for(var i=0;i<keys.length;i++){
|
|
408
|
+
var p=String(keys[i].provider||''); var status=String(keys[i].status||'active');
|
|
409
|
+
var cd=keys[i].cooldown_until?Date.parse(String(keys[i].cooldown_until)):0;
|
|
410
|
+
if(!supported[p]||status!=='active'||(cd&&cd>Date.now())||seen[p]) continue;
|
|
411
|
+
seen[p]=1; providers.push(p);
|
|
412
|
+
}
|
|
413
|
+
if(provSelect){
|
|
414
|
+
provSelect.innerHTML='<option value="">Auto</option>'+providers.map(function(p){ return '<option value="'+p+'">'+p.charAt(0).toUpperCase()+p.slice(1)+'</option>'; }).join('');
|
|
415
|
+
}
|
|
416
|
+
if(provWrap) provWrap.setAttribute('data-visible', providers.length>1?'true':'false');
|
|
417
|
+
if(keyNote) keyNote.hidden=providers.length>0;
|
|
418
|
+
}).catch(function(){});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── history ────────────────────────────────────────────────────────────────
|
|
422
|
+
function renderHistory(){
|
|
423
|
+
if(!historyList) return;
|
|
424
|
+
if(!historyItems.length){ historyList.innerHTML='<div class="rk-inpaint-hempty">No edits yet.</div>'; return; }
|
|
425
|
+
historyList.innerHTML='';
|
|
426
|
+
historyItems.forEach(function(it){
|
|
427
|
+
var row=document.createElement('div'); row.className='rk-inpaint-hitem';
|
|
428
|
+
var thumb=document.createElement('div'); thumb.className='rk-inpaint-hthumb';
|
|
429
|
+
if(it.kind==='pending'){ thumb.innerHTML='<span class="rk-inpaint-hspin" aria-hidden="true"></span>'; }
|
|
430
|
+
else { var im=document.createElement('img'); im.src=it.url; im.alt='Edit output'; im.loading='lazy'; thumb.appendChild(im); }
|
|
431
|
+
var main=document.createElement('div'); main.className='rk-inpaint-hmain';
|
|
432
|
+
var t=document.createElement('div'); t.className='rk-inpaint-htitle';
|
|
433
|
+
t.textContent=it.kind==='pending'?'Generating\\u2026':(it.createdAt?new Date(it.createdAt).toLocaleTimeString([], {hour:'numeric',minute:'2-digit'}):'Edit output');
|
|
434
|
+
var sub=document.createElement('div'); sub.className='rk-inpaint-hsub';
|
|
435
|
+
sub.textContent=it.kind==='pending'?('Job '+(it.status||'running')+(it.progress>0?(' \\u00b7 '+Math.round(it.progress*100)+'%'):'')):'Ready';
|
|
436
|
+
main.appendChild(t); main.appendChild(sub);
|
|
437
|
+
row.appendChild(thumb); row.appendChild(main);
|
|
438
|
+
if(it.kind==='output'&&it.url){
|
|
439
|
+
var dl=document.createElement('a'); dl.className='rk-inpaint-hdl'; dl.href=it.url; dl.target='_blank'; dl.rel='noopener';
|
|
440
|
+
dl.setAttribute('download','inpaint-'+(it.jobId||'output')+'.png'); dl.setAttribute('aria-label','Download'); dl.innerHTML=${JSON.stringify(DL_ICON)};
|
|
441
|
+
row.appendChild(dl);
|
|
442
|
+
}
|
|
443
|
+
historyList.appendChild(row);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function upsertPending(jobId,status,progress){
|
|
447
|
+
if(!jobId) return;
|
|
448
|
+
historyItems=historyItems.filter(function(x){ return x.jobId!==jobId; });
|
|
449
|
+
historyItems.unshift({ kind:'pending', jobId:jobId, status:status||'queued', progress:progress||0, createdAt:new Date().toISOString() });
|
|
450
|
+
renderHistory();
|
|
451
|
+
}
|
|
452
|
+
function upsertOutput(jobId,url){
|
|
453
|
+
historyItems=historyItems.filter(function(x){ return x.jobId!==jobId; });
|
|
454
|
+
historyItems.unshift({ kind:'output', jobId:jobId, url:url, createdAt:new Date().toISOString() });
|
|
455
|
+
renderHistory();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function extractOutput(payload){
|
|
459
|
+
var r=(payload&&payload.result)?payload.result:{};
|
|
460
|
+
return (r.output&&r.output.image&&r.output.image.file_url)
|
|
461
|
+
||(r.output&&r.output.primary_file_url)
|
|
462
|
+
||(r.image&&r.image.file_url)
|
|
463
|
+
||r.primary_file_url
|
|
464
|
+
||(r.output&&Array.isArray(r.output.files)?r.output.files[0]:'')
|
|
465
|
+
||(Array.isArray(r.files)?r.files[0]:'')
|
|
466
|
+
||'';
|
|
467
|
+
}
|
|
468
|
+
function applyCompleted(payload){
|
|
469
|
+
var out=extractOutput(payload); if(!out) return '';
|
|
470
|
+
var url=absUrl(out); var jobId=String((payload&&(payload.job_id||payload.jobId))||'');
|
|
471
|
+
if(resultImg) resultImg.src=url;
|
|
472
|
+
if(resultLink) resultLink.href=url;
|
|
473
|
+
if(resultDl){ resultDl.href=url; resultDl.setAttribute('download','inpaint-'+Date.now()+'.png'); }
|
|
474
|
+
if(resultBox) resultBox.hidden=false;
|
|
475
|
+
upsertOutput(jobId||'inpaint', url);
|
|
476
|
+
return url;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function pollJob(jobId){
|
|
480
|
+
var attempt=0;
|
|
481
|
+
function step(){
|
|
482
|
+
return fetch('/api/v1/primitives/image_inpaint/jobs/'+encodeURIComponent(jobId),{credentials:'same-origin'}).then(function(r){
|
|
483
|
+
return parseJson(r).then(function(payload){
|
|
484
|
+
if(!r.ok) throw new Error(payload.error||'Unable to poll image edit job.');
|
|
485
|
+
if(payload.status==='failed'||payload.status==='cancelled'){ throw new Error((payload.error&&payload.error.message)||('Image edit job '+payload.status+'.')); }
|
|
486
|
+
var out=extractOutput(payload);
|
|
487
|
+
if(payload.status==='succeeded'||payload.status==='complete'||out){
|
|
488
|
+
if(!out) throw new Error('Image edit completed without an output URL.');
|
|
489
|
+
applyCompleted(payload); setStatus('Image edit complete.','success'); return out;
|
|
490
|
+
}
|
|
491
|
+
upsertPending(jobId,payload.status,Number(payload.progress||0));
|
|
492
|
+
setStatus('Image edit job is '+(payload.status||'running')+'\\u2026','');
|
|
493
|
+
attempt++;
|
|
494
|
+
if(attempt>=90){ setStatus('Still running \\u2014 check back shortly.',''); return null; }
|
|
495
|
+
return new Promise(function(res){ setTimeout(res, attempt<8?1500:3000); }).then(step);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return step();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function submit(){
|
|
503
|
+
if(!canGenerate()){
|
|
504
|
+
if(!sourceUrl) setStatus('Upload a source image first.','error');
|
|
505
|
+
else if(!strokes.length) setStatus('Brush at least one mask region.','error');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if(!(promptEl.value||'').trim()){ setStatus('Describe what should change in the masked area.','error'); return; }
|
|
509
|
+
var instruction=buildInstruction(); if(!instruction){ setStatus('Describe what should change in the masked area.','error'); return; }
|
|
510
|
+
busy=true; syncGenerate();
|
|
511
|
+
setStatus('Uploading mask\\u2026','');
|
|
512
|
+
maskBlob().then(function(blob){ return uploadTemp(blob,'inpaint-mask-'+Date.now()+'.png','image/png'); }).then(function(maskUrl){
|
|
513
|
+
setStatus('Creating image edit job\\u2026','');
|
|
514
|
+
var payload={ source_image_url:sourceUrl, mask_url:absUrl(maskUrl), instruction:instruction, reference_attachments:[], size:'auto', output_format:'png' };
|
|
515
|
+
if(provSelect&&provSelect.value) payload.provider=provSelect.value;
|
|
516
|
+
var ar=aspectRatio(); if(ar) payload.aspect_ratio=ar;
|
|
517
|
+
return fetch('/api/v1/primitives/images/inpaint',{
|
|
518
|
+
method:'POST',credentials:'same-origin',headers:{'content-type':'application/json'},
|
|
519
|
+
body:JSON.stringify({ tracer:tracerId(), payload:payload })
|
|
520
|
+
}).then(parseJson).then(function(j){
|
|
521
|
+
if(!j||!j.job_id){ throw new Error((j&&j.error)||'Unable to create image edit job.'); }
|
|
522
|
+
setStatus('Image edit job started\\u2026','success');
|
|
523
|
+
upsertPending(j.job_id,'queued',0);
|
|
524
|
+
return pollJob(j.job_id);
|
|
525
|
+
});
|
|
526
|
+
}).catch(function(err){
|
|
527
|
+
var msg=(err&&err.message)?err.message:'Unable to create image edit.';
|
|
528
|
+
if(/401|403|sign in|unauth/i.test(msg)) msg='Sign in to generate edits.';
|
|
529
|
+
setStatus(msg,'error');
|
|
530
|
+
}).then(function(){ busy=false; syncGenerate(); });
|
|
531
|
+
}
|
|
532
|
+
if(generate) generate.addEventListener('click',submit);
|
|
533
|
+
|
|
534
|
+
renderHistory();
|
|
535
|
+
loadProviders();
|
|
536
|
+
syncGenerate();
|
|
537
|
+
})();`;
|
|
538
|
+
return reskinDocument({
|
|
539
|
+
title: "vidfarm reskin — Create Image",
|
|
540
|
+
description: "Reskinned vidfarm masked image editor — upload, brush a mask, describe the edit, and generate on your own AI keys.",
|
|
541
|
+
activeSlug: "inpaint",
|
|
542
|
+
pageCss,
|
|
543
|
+
body,
|
|
544
|
+
script
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
//# sourceMappingURL=inpaint-page.js.map
|