@mevdragon/vidfarm-devcli 0.2.14 → 0.3.0
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/README.md +11 -4
- package/SKILL.director.md +97 -0
- package/SKILL.platform.md +66 -6
- package/dist/src/cli.js +135 -1527
- package/package.json +5 -24
- package/.env.example +0 -60
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +0 -275
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +0 -128
- package/auto-create-hyperframe-templates/input-manifest.schema.json +0 -56
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +0 -118
- package/auto-create-hyperframe-templates/production-graph.schema.json +0 -314
- package/auto-create-hyperframe-templates/runbook.md +0 -198
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +0 -368
- package/auto-create-hyperframe-templates/template-plan.schema.json +0 -311
- package/auto-create-hyperframe-templates/template-planner-prompt.md +0 -144
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +0 -599
- package/auto-create-templates/extractor-system-prompt.md +0 -119
- package/auto-create-templates/production-graph.schema.json +0 -276
- package/auto-create-templates/runbook.md +0 -140
- package/auto-create-templates/template-plan.schema.json +0 -301
- package/auto-create-templates/template-planner-prompt.md +0 -137
- package/dist/src/account-pages-legacy.js +0 -9338
- package/dist/src/account-pages-legacy.js.map +0 -1
- package/dist/src/account-pages.js +0 -52
- package/dist/src/account-pages.js.map +0 -1
- package/dist/src/app.js +0 -13711
- package/dist/src/app.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/composition-runtime.js +0 -581
- package/dist/src/composition-runtime.js.map +0 -1
- package/dist/src/config.js +0 -140
- package/dist/src/config.js.map +0 -1
- package/dist/src/context.js +0 -492
- package/dist/src/context.js.map +0 -1
- package/dist/src/dev-app-legacy.js +0 -694
- package/dist/src/dev-app-legacy.js.map +0 -1
- package/dist/src/dev-app.js +0 -6
- package/dist/src/dev-app.js.map +0 -1
- package/dist/src/dev-serve.js.map +0 -1
- package/dist/src/domain.js +0 -2
- package/dist/src/domain.js.map +0 -1
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat-history.js.map +0 -1
- package/dist/src/editor-chat.js +0 -312
- package/dist/src/editor-chat.js.map +0 -1
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/editor-dark-theme.js.map +0 -1
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/flockposter-cache-store.js.map +0 -1
- package/dist/src/frontend/homepage-client.js +0 -117
- package/dist/src/frontend/homepage-client.js.map +0 -1
- package/dist/src/frontend/homepage-shared.js +0 -4
- package/dist/src/frontend/homepage-shared.js.map +0 -1
- package/dist/src/frontend/homepage-store.js +0 -28
- package/dist/src/frontend/homepage-store.js.map +0 -1
- package/dist/src/frontend/homepage-view.js +0 -260
- package/dist/src/frontend/homepage-view.js.map +0 -1
- package/dist/src/frontend/page-runtime-client.js +0 -124
- package/dist/src/frontend/page-runtime-client.js.map +0 -1
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/page-runtime-store.js.map +0 -1
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/sentry.js.map +0 -1
- package/dist/src/frontend/template-editor-chat.js +0 -3760
- package/dist/src/frontend/template-editor-chat.js.map +0 -1
- package/dist/src/help-page.js +0 -333
- package/dist/src/help-page.js.map +0 -1
- package/dist/src/homepage.js +0 -919
- package/dist/src/homepage.js.map +0 -1
- package/dist/src/hyperframes/composition.js +0 -180
- package/dist/src/hyperframes/composition.js.map +0 -1
- package/dist/src/index.js +0 -16
- package/dist/src/index.js.map +0 -1
- package/dist/src/instrument.js +0 -30
- package/dist/src/instrument.js.map +0 -1
- package/dist/src/lib/crypto.js +0 -45
- package/dist/src/lib/crypto.js.map +0 -1
- package/dist/src/lib/dev-log.js +0 -54
- package/dist/src/lib/dev-log.js.map +0 -1
- package/dist/src/lib/display-name.js +0 -11
- package/dist/src/lib/display-name.js.map +0 -1
- package/dist/src/lib/ids.js +0 -24
- package/dist/src/lib/ids.js.map +0 -1
- package/dist/src/lib/images.js +0 -19
- package/dist/src/lib/images.js.map +0 -1
- package/dist/src/lib/json.js +0 -15
- package/dist/src/lib/json.js.map +0 -1
- package/dist/src/lib/template-dna.js +0 -543
- package/dist/src/lib/template-dna.js.map +0 -1
- package/dist/src/lib/template-paths.js +0 -28
- package/dist/src/lib/template-paths.js.map +0 -1
- package/dist/src/lib/template-style-options.js +0 -203
- package/dist/src/lib/template-style-options.js.map +0 -1
- package/dist/src/lib/time.js +0 -7
- package/dist/src/lib/time.js.map +0 -1
- package/dist/src/lib/video-quality-harness.js +0 -60
- package/dist/src/lib/video-quality-harness.js.map +0 -1
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-runtime.js.map +0 -1
- package/dist/src/page-shell.js +0 -1315
- package/dist/src/page-shell.js.map +0 -1
- package/dist/src/primitive-context.js +0 -299
- package/dist/src/primitive-context.js.map +0 -1
- package/dist/src/primitive-registry.js +0 -2181
- package/dist/src/primitive-registry.js.map +0 -1
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitive-sdk.js.map +0 -1
- package/dist/src/primitives/remotion/html-image.js +0 -30
- package/dist/src/primitives/remotion/html-image.js.map +0 -1
- package/dist/src/primitives/remotion/media-slideshow.js +0 -60
- package/dist/src/primitives/remotion/media-slideshow.js.map +0 -1
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/react-page-shell.js.map +0 -1
- package/dist/src/ready-post-schedule-component.js +0 -1515
- package/dist/src/ready-post-schedule-component.js.map +0 -1
- package/dist/src/registry.js +0 -296
- package/dist/src/registry.js.map +0 -1
- package/dist/src/runtime.js +0 -31
- package/dist/src/runtime.js.map +0 -1
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/api-call-history.js.map +0 -1
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/auth.js.map +0 -1
- package/dist/src/services/billing.js +0 -231
- package/dist/src/services/billing.js.map +0 -1
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/chat-threads.js.map +0 -1
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-access.js.map +0 -1
- package/dist/src/services/fork-manifest.js +0 -43
- package/dist/src/services/fork-manifest.js.map +0 -1
- package/dist/src/services/ghostcut.js +0 -132
- package/dist/src/services/ghostcut.js.map +0 -1
- package/dist/src/services/hyperframes.js +0 -1014
- package/dist/src/services/hyperframes.js.map +0 -1
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-capacity.js.map +0 -1
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/job-logs.js.map +0 -1
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/jobs.js.map +0 -1
- package/dist/src/services/media-processing.js +0 -743
- package/dist/src/services/media-processing.js.map +0 -1
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/primitive-media-lambda.js.map +0 -1
- package/dist/src/services/providers.js +0 -2870
- package/dist/src/services/providers.js.map +0 -1
- package/dist/src/services/rate-limits.js +0 -237
- package/dist/src/services/rate-limits.js.map +0 -1
- package/dist/src/services/remotion.js +0 -688
- package/dist/src/services/remotion.js.map +0 -1
- package/dist/src/services/serverless-auth.js +0 -374
- package/dist/src/services/serverless-auth.js.map +0 -1
- package/dist/src/services/serverless-jobs.js +0 -1074
- package/dist/src/services/serverless-jobs.js.map +0 -1
- package/dist/src/services/serverless-provider-keys.js +0 -401
- package/dist/src/services/serverless-provider-keys.js.map +0 -1
- package/dist/src/services/serverless-records.js +0 -1088
- package/dist/src/services/serverless-records.js.map +0 -1
- package/dist/src/services/serverless-template-configs.js +0 -67
- package/dist/src/services/serverless-template-configs.js.map +0 -1
- package/dist/src/services/storage.js +0 -383
- package/dist/src/services/storage.js.map +0 -1
- package/dist/src/services/template-certification.js +0 -475
- package/dist/src/services/template-certification.js.map +0 -1
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-loader.js.map +0 -1
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-runtime-bundles.js.map +0 -1
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/template-sources.js.map +0 -1
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/video-normalization.js.map +0 -1
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/services/webhooks.js.map +0 -1
- package/dist/src/template-editor-pages.js +0 -2491
- package/dist/src/template-editor-pages.js.map +0 -1
- package/dist/src/template-editor-shell.js +0 -2702
- package/dist/src/template-editor-shell.js.map +0 -1
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/template-sdk.js.map +0 -1
- package/dist/src/worker.js +0 -17
- package/dist/src/worker.js.map +0 -1
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/readme.secret.md +0 -89
- package/templates/vidfarm_template_0000/README.md +0 -106
- package/templates/vidfarm_template_0000/SKILL.md +0 -266
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +0 -11
- package/templates/vidfarm_template_0000/src/lib/images.js +0 -221
- package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
- package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/templates/vidfarm_template_0000/src/style-options.js +0 -200
- package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/templates/vidfarm_template_0000/src/template.js +0 -1566
- package/templates/vidfarm_template_0000/template.config.json +0 -21
|
@@ -1,2491 +0,0 @@
|
|
|
1
|
-
import { renderBrandLockup, renderPageShell, renderPrimaryNav } from "./page-shell.js";
|
|
2
|
-
export function renderCustomInpaintPage(input) {
|
|
3
|
-
return renderPageShell({
|
|
4
|
-
title: "Inpaint",
|
|
5
|
-
mainClass: "is-wide",
|
|
6
|
-
style: `
|
|
7
|
-
.custom-inpaint-frame {
|
|
8
|
-
height: 100%;
|
|
9
|
-
min-height: 0;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.custom-inpaint-shell {
|
|
13
|
-
height: 100%;
|
|
14
|
-
min-height: 0;
|
|
15
|
-
display: grid;
|
|
16
|
-
grid-template-rows: auto minmax(0, 1fr);
|
|
17
|
-
gap: 14px;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.custom-inpaint-workbench {
|
|
21
|
-
height: 100%;
|
|
22
|
-
min-height: 0;
|
|
23
|
-
display: grid;
|
|
24
|
-
grid-template-columns: 280px minmax(0, 1fr) 340px;
|
|
25
|
-
gap: 14px;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.custom-inpaint-panel,
|
|
30
|
-
.custom-inpaint-stage,
|
|
31
|
-
.custom-inpaint-output,
|
|
32
|
-
.editor-inpaint-panel {
|
|
33
|
-
border: 1px solid rgba(191, 164, 109, 0.24);
|
|
34
|
-
border-radius: 18px;
|
|
35
|
-
background: rgba(255, 255, 255, 0.74);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.custom-inpaint-panel {
|
|
39
|
-
min-height: 0;
|
|
40
|
-
display: grid;
|
|
41
|
-
align-content: start;
|
|
42
|
-
gap: 12px;
|
|
43
|
-
padding: 14px;
|
|
44
|
-
overflow: auto;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.custom-inpaint-panel h1,
|
|
48
|
-
.custom-inpaint-output h2 {
|
|
49
|
-
margin: 0;
|
|
50
|
-
color: #253041;
|
|
51
|
-
font-size: 1.02rem;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.custom-inpaint-panel p,
|
|
55
|
-
.custom-inpaint-status,
|
|
56
|
-
.editor-inpaint-label {
|
|
57
|
-
margin: 0;
|
|
58
|
-
color: #617087;
|
|
59
|
-
font-size: 0.8rem;
|
|
60
|
-
line-height: 1.45;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.custom-inpaint-panel-head,
|
|
64
|
-
.custom-inpaint-tool-head {
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
justify-content: space-between;
|
|
68
|
-
gap: 10px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.custom-inpaint-upload {
|
|
72
|
-
min-height: 96px;
|
|
73
|
-
display: grid;
|
|
74
|
-
place-items: center;
|
|
75
|
-
gap: 5px;
|
|
76
|
-
padding: 12px;
|
|
77
|
-
border: 1px dashed rgba(97, 112, 135, 0.36);
|
|
78
|
-
border-radius: 16px;
|
|
79
|
-
background: rgba(255, 251, 244, 0.72);
|
|
80
|
-
color: #253041;
|
|
81
|
-
cursor: pointer;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.custom-inpaint-upload svg {
|
|
85
|
-
width: 22px;
|
|
86
|
-
height: 22px;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.custom-inpaint-upload span {
|
|
90
|
-
max-width: 100%;
|
|
91
|
-
overflow-wrap: anywhere;
|
|
92
|
-
word-break: break-word;
|
|
93
|
-
text-align: center;
|
|
94
|
-
line-height: 1.25;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.custom-inpaint-stage {
|
|
98
|
-
position: relative;
|
|
99
|
-
min-height: 0;
|
|
100
|
-
display: grid;
|
|
101
|
-
place-items: center;
|
|
102
|
-
overflow: auto;
|
|
103
|
-
background:
|
|
104
|
-
linear-gradient(45deg, rgba(255,255,255,0.06) 25%, transparent 25%),
|
|
105
|
-
linear-gradient(-45deg, rgba(255,255,255,0.06) 25%, transparent 25%),
|
|
106
|
-
linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.06) 75%),
|
|
107
|
-
linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.06) 75%),
|
|
108
|
-
#252d3d;
|
|
109
|
-
background-size: 22px 22px;
|
|
110
|
-
background-position: 0 0, 0 11px, 11px -11px, -11px 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.custom-inpaint-empty {
|
|
114
|
-
position: absolute;
|
|
115
|
-
inset: 0;
|
|
116
|
-
z-index: 1;
|
|
117
|
-
display: grid;
|
|
118
|
-
place-items: center;
|
|
119
|
-
padding: 24px;
|
|
120
|
-
text-align: center;
|
|
121
|
-
color: rgba(255, 251, 244, 0.74);
|
|
122
|
-
font-weight: 700;
|
|
123
|
-
pointer-events: none;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.custom-inpaint-empty[hidden] {
|
|
127
|
-
display: none;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.editor-inpaint-canvas-wrap {
|
|
131
|
-
position: relative;
|
|
132
|
-
display: inline-block;
|
|
133
|
-
max-width: 100%;
|
|
134
|
-
line-height: 0;
|
|
135
|
-
--inpaint-brush-cursor-size: 70px;
|
|
136
|
-
--inpaint-brush-cursor-x: 0px;
|
|
137
|
-
--inpaint-brush-cursor-y: 0px;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.editor-inpaint-canvas-wrap img {
|
|
141
|
-
max-width: 100%;
|
|
142
|
-
max-height: calc(100vh - 180px);
|
|
143
|
-
display: block;
|
|
144
|
-
object-fit: contain;
|
|
145
|
-
user-select: none;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.editor-inpaint-canvas-wrap canvas {
|
|
149
|
-
position: absolute;
|
|
150
|
-
inset: 0;
|
|
151
|
-
width: 100%;
|
|
152
|
-
height: 100%;
|
|
153
|
-
opacity: 0.86;
|
|
154
|
-
cursor: none;
|
|
155
|
-
touch-action: none;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.editor-inpaint-brush-cursor {
|
|
159
|
-
position: absolute;
|
|
160
|
-
z-index: 2;
|
|
161
|
-
left: 0;
|
|
162
|
-
top: 0;
|
|
163
|
-
width: var(--inpaint-brush-cursor-size);
|
|
164
|
-
height: var(--inpaint-brush-cursor-size);
|
|
165
|
-
border: 2px solid rgba(255, 255, 255, 0.92);
|
|
166
|
-
border-radius: 999px;
|
|
167
|
-
box-shadow:
|
|
168
|
-
0 0 0 1px rgba(37, 48, 65, 0.78),
|
|
169
|
-
0 0 18px rgba(37, 48, 65, 0.28);
|
|
170
|
-
opacity: 0;
|
|
171
|
-
pointer-events: none;
|
|
172
|
-
transform: translate(var(--inpaint-brush-cursor-x), var(--inpaint-brush-cursor-y)) translate(-50%, -50%);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.editor-inpaint-brush-cursor[data-visible="true"] {
|
|
176
|
-
opacity: 1;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.editor-inpaint-panel {
|
|
180
|
-
height: 100%;
|
|
181
|
-
max-height: calc(100vh - 150px);
|
|
182
|
-
min-height: 0;
|
|
183
|
-
display: grid;
|
|
184
|
-
grid-template-rows: auto;
|
|
185
|
-
align-content: start;
|
|
186
|
-
gap: 12px;
|
|
187
|
-
overflow-x: hidden;
|
|
188
|
-
overflow-y: auto;
|
|
189
|
-
overscroll-behavior: contain;
|
|
190
|
-
padding: 12px;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.editor-inpaint-toolbar,
|
|
194
|
-
.editor-inpaint-color-row,
|
|
195
|
-
.custom-inpaint-actions {
|
|
196
|
-
display: flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
gap: 10px;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.editor-inpaint-toolbar,
|
|
202
|
-
.custom-inpaint-actions {
|
|
203
|
-
flex-wrap: wrap;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.editor-inpaint-brush {
|
|
207
|
-
display: grid;
|
|
208
|
-
gap: 6px;
|
|
209
|
-
min-width: 150px;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.editor-inpaint-brush input[type="range"] {
|
|
213
|
-
width: 100%;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.editor-inpaint-color-picker {
|
|
217
|
-
width: 42px;
|
|
218
|
-
height: 38px;
|
|
219
|
-
padding: 2px;
|
|
220
|
-
border: 1px solid rgba(97, 112, 135, 0.24);
|
|
221
|
-
border-radius: 12px;
|
|
222
|
-
background: #fff;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.editor-inpaint-swatches {
|
|
226
|
-
display: none;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.editor-inpaint-prompts {
|
|
230
|
-
display: grid;
|
|
231
|
-
align-content: start;
|
|
232
|
-
gap: 10px;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.editor-inpaint-provider {
|
|
236
|
-
display: none;
|
|
237
|
-
gap: 6px;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.editor-inpaint-provider[data-visible="true"] {
|
|
241
|
-
display: grid;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.editor-inpaint-provider select {
|
|
245
|
-
min-height: 38px;
|
|
246
|
-
padding: 0 34px 0 12px;
|
|
247
|
-
border-radius: 12px;
|
|
248
|
-
border: 1px solid rgba(191, 164, 109, 0.34);
|
|
249
|
-
background: rgba(255, 255, 255, 0.96);
|
|
250
|
-
color: #243041;
|
|
251
|
-
font: inherit;
|
|
252
|
-
font-weight: 800;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.editor-inpaint-color-row {
|
|
256
|
-
position: relative;
|
|
257
|
-
align-items: flex-start;
|
|
258
|
-
padding: 4px;
|
|
259
|
-
border-radius: 16px;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.editor-inpaint-color-tools {
|
|
263
|
-
flex: 0 0 auto;
|
|
264
|
-
display: grid;
|
|
265
|
-
justify-items: center;
|
|
266
|
-
gap: 6px;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.editor-inpaint-prompt-stack {
|
|
270
|
-
min-width: 0;
|
|
271
|
-
flex: 1 1 auto;
|
|
272
|
-
display: grid;
|
|
273
|
-
align-content: start;
|
|
274
|
-
gap: 8px;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.editor-inpaint-global {
|
|
278
|
-
display: grid;
|
|
279
|
-
align-content: start;
|
|
280
|
-
gap: 8px;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.editor-inpaint-prompt-field {
|
|
284
|
-
position: relative;
|
|
285
|
-
min-width: 0;
|
|
286
|
-
display: grid;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.editor-inpaint-attachment-tools {
|
|
290
|
-
display: flex;
|
|
291
|
-
align-items: center;
|
|
292
|
-
flex-wrap: wrap;
|
|
293
|
-
gap: 6px;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.editor-inpaint-attach-button {
|
|
297
|
-
position: absolute;
|
|
298
|
-
right: 8px;
|
|
299
|
-
bottom: 8px;
|
|
300
|
-
transform: translateY(-10px);
|
|
301
|
-
width: 30px;
|
|
302
|
-
height: 30px;
|
|
303
|
-
display: inline-grid;
|
|
304
|
-
place-items: center;
|
|
305
|
-
padding: 0;
|
|
306
|
-
border-radius: 999px;
|
|
307
|
-
border: 1px solid transparent;
|
|
308
|
-
background: rgba(255, 255, 255, 0.72);
|
|
309
|
-
color: rgba(37, 48, 65, 0.56);
|
|
310
|
-
font: inherit;
|
|
311
|
-
cursor: pointer;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.editor-inpaint-attach-button:hover,
|
|
315
|
-
.editor-inpaint-attach-button:focus-visible {
|
|
316
|
-
border-color: rgba(191, 164, 109, 0.34);
|
|
317
|
-
background: rgba(255, 251, 244, 0.94);
|
|
318
|
-
color: #253041;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.editor-inpaint-attach-button svg {
|
|
322
|
-
width: 16px;
|
|
323
|
-
height: 16px;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.editor-inpaint-attachment-chips {
|
|
327
|
-
display: flex;
|
|
328
|
-
align-items: center;
|
|
329
|
-
flex-wrap: wrap;
|
|
330
|
-
gap: 6px;
|
|
331
|
-
min-width: 0;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.editor-inpaint-attachment-chip {
|
|
335
|
-
min-width: 0;
|
|
336
|
-
max-width: 210px;
|
|
337
|
-
display: inline-flex;
|
|
338
|
-
align-items: center;
|
|
339
|
-
gap: 4px;
|
|
340
|
-
padding: 1px 4px 1px 1px;
|
|
341
|
-
border-radius: 999px;
|
|
342
|
-
border: 1px solid rgba(191, 164, 109, 0.3);
|
|
343
|
-
background: rgba(247, 241, 230, 0.82);
|
|
344
|
-
color: #253041;
|
|
345
|
-
font-size: 0.74rem;
|
|
346
|
-
font-weight: 800;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.editor-inpaint-attachment-chip img {
|
|
350
|
-
width: 22px;
|
|
351
|
-
height: 22px;
|
|
352
|
-
min-width: 22px;
|
|
353
|
-
max-width: 22px;
|
|
354
|
-
aspect-ratio: 1 / 1;
|
|
355
|
-
flex: 0 0 22px;
|
|
356
|
-
object-fit: cover;
|
|
357
|
-
border-radius: 999px;
|
|
358
|
-
background: rgba(37, 48, 65, 0.08);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.editor-inpaint-attachment-chip span {
|
|
362
|
-
min-width: 0;
|
|
363
|
-
overflow: hidden;
|
|
364
|
-
text-overflow: ellipsis;
|
|
365
|
-
white-space: nowrap;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
.editor-inpaint-attachment-chip button {
|
|
369
|
-
width: 18px;
|
|
370
|
-
height: 18px;
|
|
371
|
-
min-width: 18px;
|
|
372
|
-
max-width: 18px;
|
|
373
|
-
aspect-ratio: 1 / 1;
|
|
374
|
-
flex: 0 0 18px;
|
|
375
|
-
display: inline-grid;
|
|
376
|
-
place-items: center;
|
|
377
|
-
padding: 0;
|
|
378
|
-
border: 0;
|
|
379
|
-
border-radius: 999px;
|
|
380
|
-
background: rgba(37, 48, 65, 0.1);
|
|
381
|
-
color: #253041;
|
|
382
|
-
font: inherit;
|
|
383
|
-
line-height: 1;
|
|
384
|
-
cursor: pointer;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
.editor-inpaint-color-row[data-active="true"] {
|
|
388
|
-
background: rgba(219, 193, 122, 0.16);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.editor-inpaint-color-dot {
|
|
392
|
-
width: 26px;
|
|
393
|
-
height: 26px;
|
|
394
|
-
min-width: 26px;
|
|
395
|
-
max-width: 26px;
|
|
396
|
-
aspect-ratio: 1 / 1;
|
|
397
|
-
flex: 0 0 26px;
|
|
398
|
-
margin-top: 8px;
|
|
399
|
-
border-radius: 999px;
|
|
400
|
-
border: 2px solid rgba(37, 48, 65, 0.18);
|
|
401
|
-
background: var(--inpaint-color, #ff2d55);
|
|
402
|
-
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.8);
|
|
403
|
-
cursor: pointer;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.editor-inpaint-color-remove {
|
|
407
|
-
width: 22px;
|
|
408
|
-
height: 22px;
|
|
409
|
-
min-width: 22px;
|
|
410
|
-
max-width: 22px;
|
|
411
|
-
aspect-ratio: 1 / 1;
|
|
412
|
-
flex: 0 0 22px;
|
|
413
|
-
display: inline-grid;
|
|
414
|
-
place-items: center;
|
|
415
|
-
padding: 0;
|
|
416
|
-
border: 1px solid rgba(37, 48, 65, 0.12);
|
|
417
|
-
border-radius: 999px;
|
|
418
|
-
background: rgba(255, 255, 255, 0.72);
|
|
419
|
-
color: rgba(37, 48, 65, 0.56);
|
|
420
|
-
cursor: pointer;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.editor-inpaint-color-remove:hover,
|
|
424
|
-
.editor-inpaint-color-remove:focus-visible {
|
|
425
|
-
border-color: rgba(191, 164, 109, 0.36);
|
|
426
|
-
color: #253041;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.editor-inpaint-color-remove svg {
|
|
430
|
-
width: 13px;
|
|
431
|
-
height: 13px;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.editor-inpaint-color-row[data-active="true"] .editor-inpaint-color-dot {
|
|
435
|
-
border-color: #253041;
|
|
436
|
-
box-shadow: 0 0 0 3px rgba(219, 193, 122, 0.45), inset 0 0 0 2px rgba(255, 255, 255, 0.8);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.editor-inpaint-prompt {
|
|
440
|
-
width: 100%;
|
|
441
|
-
min-height: 72px;
|
|
442
|
-
display: block;
|
|
443
|
-
resize: vertical;
|
|
444
|
-
padding: 10px 44px 18px 12px;
|
|
445
|
-
border-radius: 14px;
|
|
446
|
-
border: 1px solid rgba(191, 164, 109, 0.34);
|
|
447
|
-
background: rgba(255, 255, 255, 0.96);
|
|
448
|
-
color: #243041;
|
|
449
|
-
font: inherit;
|
|
450
|
-
line-height: 1.45;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.editor-history-button {
|
|
454
|
-
min-height: 38px;
|
|
455
|
-
display: inline-flex;
|
|
456
|
-
align-items: center;
|
|
457
|
-
justify-content: center;
|
|
458
|
-
gap: 6px;
|
|
459
|
-
padding: 0 12px;
|
|
460
|
-
border-radius: 12px;
|
|
461
|
-
border: 1px solid rgba(191, 164, 109, 0.28);
|
|
462
|
-
background: rgba(255, 255, 255, 0.86);
|
|
463
|
-
color: #253041;
|
|
464
|
-
font-size: 0.82rem;
|
|
465
|
-
font-weight: 800;
|
|
466
|
-
cursor: pointer;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
.editor-history-button.is-primary {
|
|
470
|
-
border-color: rgba(37, 48, 65, 0.9);
|
|
471
|
-
background: #253041;
|
|
472
|
-
color: #fffaf0;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
.editor-history-button[disabled] {
|
|
476
|
-
opacity: 0.58;
|
|
477
|
-
cursor: wait;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
.editor-history-button.is-icon {
|
|
481
|
-
width: 38px;
|
|
482
|
-
padding: 0;
|
|
483
|
-
flex: 0 0 auto;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
.editor-history-button.is-block {
|
|
487
|
-
width: 100%;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
.editor-history-button svg {
|
|
491
|
-
width: 18px;
|
|
492
|
-
height: 18px;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.editor-history-button[data-loading="true"]::before {
|
|
496
|
-
content: "";
|
|
497
|
-
width: 14px;
|
|
498
|
-
height: 14px;
|
|
499
|
-
border-radius: 999px;
|
|
500
|
-
border: 2px solid currentColor;
|
|
501
|
-
border-top-color: transparent;
|
|
502
|
-
animation: editor-inpaint-spin 0.75s linear infinite;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
@keyframes editor-inpaint-spin {
|
|
506
|
-
to { transform: rotate(360deg); }
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
.custom-inpaint-settings-wrap {
|
|
510
|
-
position: relative;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
.custom-inpaint-settings {
|
|
514
|
-
position: absolute;
|
|
515
|
-
right: 0;
|
|
516
|
-
top: calc(100% + 8px);
|
|
517
|
-
z-index: 20;
|
|
518
|
-
width: 260px;
|
|
519
|
-
display: grid;
|
|
520
|
-
gap: 8px;
|
|
521
|
-
padding: 12px;
|
|
522
|
-
border-radius: 16px;
|
|
523
|
-
border: 1px solid rgba(191, 164, 109, 0.32);
|
|
524
|
-
background: rgba(255, 251, 244, 0.98);
|
|
525
|
-
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.16);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
.custom-inpaint-settings[hidden],
|
|
529
|
-
.custom-inpaint-output[hidden] {
|
|
530
|
-
display: none;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
.custom-inpaint-settings input {
|
|
534
|
-
width: 100%;
|
|
535
|
-
min-height: 38px;
|
|
536
|
-
padding: 0 12px;
|
|
537
|
-
border-radius: 12px;
|
|
538
|
-
border: 1px solid rgba(191, 164, 109, 0.34);
|
|
539
|
-
background: rgba(255, 255, 255, 0.96);
|
|
540
|
-
color: #243041;
|
|
541
|
-
font: inherit;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
.custom-inpaint-output {
|
|
545
|
-
display: grid;
|
|
546
|
-
gap: 10px;
|
|
547
|
-
padding: 12px;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
.custom-inpaint-preview {
|
|
551
|
-
width: 100%;
|
|
552
|
-
display: grid;
|
|
553
|
-
padding: 0;
|
|
554
|
-
border: 0;
|
|
555
|
-
border-radius: 12px;
|
|
556
|
-
background: #252d3d;
|
|
557
|
-
cursor: zoom-in;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
.custom-inpaint-output img {
|
|
561
|
-
width: 100%;
|
|
562
|
-
max-height: 320px;
|
|
563
|
-
object-fit: contain;
|
|
564
|
-
border-radius: 12px;
|
|
565
|
-
background: #252d3d;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
.custom-inpaint-history {
|
|
569
|
-
display: grid;
|
|
570
|
-
gap: 8px;
|
|
571
|
-
padding: 12px;
|
|
572
|
-
border: 1px solid rgba(191, 164, 109, 0.24);
|
|
573
|
-
border-radius: 18px;
|
|
574
|
-
background: rgba(255, 255, 255, 0.58);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
.custom-inpaint-history-head {
|
|
578
|
-
display: flex;
|
|
579
|
-
align-items: center;
|
|
580
|
-
justify-content: space-between;
|
|
581
|
-
gap: 8px;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
.custom-inpaint-history-head h2 {
|
|
585
|
-
margin: 0;
|
|
586
|
-
color: #253041;
|
|
587
|
-
font-size: 1rem;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
.custom-inpaint-history-refresh {
|
|
591
|
-
width: 30px;
|
|
592
|
-
height: 30px;
|
|
593
|
-
min-width: 30px;
|
|
594
|
-
padding: 0;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
.custom-inpaint-history-refresh svg {
|
|
598
|
-
width: 15px;
|
|
599
|
-
height: 15px;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.custom-inpaint-history-list {
|
|
603
|
-
display: grid;
|
|
604
|
-
gap: 8px;
|
|
605
|
-
overflow: visible;
|
|
606
|
-
padding: 2px;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
.custom-inpaint-history-empty {
|
|
610
|
-
color: #617087;
|
|
611
|
-
font-size: 0.8rem;
|
|
612
|
-
line-height: 1.4;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.custom-inpaint-history-item {
|
|
616
|
-
position: relative;
|
|
617
|
-
min-width: 0;
|
|
618
|
-
display: grid;
|
|
619
|
-
grid-template-columns: 54px minmax(0, 1fr);
|
|
620
|
-
align-items: center;
|
|
621
|
-
gap: 8px;
|
|
622
|
-
padding: 6px;
|
|
623
|
-
border: 1px solid rgba(191, 164, 109, 0.2);
|
|
624
|
-
border-radius: 12px;
|
|
625
|
-
background: rgba(255, 251, 244, 0.62);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
.custom-inpaint-history-thumb-button {
|
|
629
|
-
width: 54px;
|
|
630
|
-
height: 54px;
|
|
631
|
-
display: grid;
|
|
632
|
-
padding: 0;
|
|
633
|
-
border: 0;
|
|
634
|
-
border-radius: 10px;
|
|
635
|
-
background: transparent;
|
|
636
|
-
cursor: zoom-in;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
.custom-inpaint-history-thumb {
|
|
640
|
-
width: 54px;
|
|
641
|
-
height: 54px;
|
|
642
|
-
border: 1px solid rgba(191, 164, 109, 0.3);
|
|
643
|
-
border-radius: 10px;
|
|
644
|
-
object-fit: cover;
|
|
645
|
-
background: #252d3d;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
.custom-inpaint-history-generating {
|
|
649
|
-
width: 54px;
|
|
650
|
-
height: 54px;
|
|
651
|
-
display: grid;
|
|
652
|
-
place-items: center;
|
|
653
|
-
border: 1px solid rgba(191, 164, 109, 0.3);
|
|
654
|
-
border-radius: 10px;
|
|
655
|
-
background: linear-gradient(135deg, rgba(37, 48, 65, 0.92), rgba(122, 75, 22, 0.84));
|
|
656
|
-
color: #fff7e8;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
.custom-inpaint-history-generating::before {
|
|
660
|
-
content: "";
|
|
661
|
-
width: 22px;
|
|
662
|
-
height: 22px;
|
|
663
|
-
border-radius: 999px;
|
|
664
|
-
border: 2px solid rgba(255, 247, 232, 0.34);
|
|
665
|
-
border-top-color: #fff7e8;
|
|
666
|
-
animation: editor-inpaint-spin 0.75s linear infinite;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
.custom-inpaint-history-meta {
|
|
670
|
-
min-width: 0;
|
|
671
|
-
display: grid;
|
|
672
|
-
gap: 5px;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
.custom-inpaint-history-title {
|
|
676
|
-
min-width: 0;
|
|
677
|
-
color: #253041;
|
|
678
|
-
font-size: 0.78rem;
|
|
679
|
-
font-weight: 800;
|
|
680
|
-
overflow: hidden;
|
|
681
|
-
text-overflow: ellipsis;
|
|
682
|
-
white-space: nowrap;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
.custom-inpaint-history-subtitle {
|
|
686
|
-
min-width: 0;
|
|
687
|
-
color: #617087;
|
|
688
|
-
font-size: 0.72rem;
|
|
689
|
-
font-weight: 700;
|
|
690
|
-
overflow: hidden;
|
|
691
|
-
text-overflow: ellipsis;
|
|
692
|
-
white-space: nowrap;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
.custom-inpaint-history-actions {
|
|
696
|
-
display: flex;
|
|
697
|
-
align-items: center;
|
|
698
|
-
justify-content: flex-start;
|
|
699
|
-
gap: 4px;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
.custom-inpaint-history-icon {
|
|
703
|
-
width: 24px;
|
|
704
|
-
height: 24px;
|
|
705
|
-
display: inline-grid;
|
|
706
|
-
place-items: center;
|
|
707
|
-
padding: 0;
|
|
708
|
-
border: 1px solid rgba(191, 164, 109, 0.24);
|
|
709
|
-
border-radius: 999px;
|
|
710
|
-
background: rgba(255, 255, 255, 0.88);
|
|
711
|
-
color: #253041;
|
|
712
|
-
cursor: pointer;
|
|
713
|
-
text-decoration: none;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.custom-inpaint-history-check {
|
|
717
|
-
min-height: 24px;
|
|
718
|
-
padding: 0 9px;
|
|
719
|
-
border: 1px solid rgba(191, 164, 109, 0.3);
|
|
720
|
-
border-radius: 999px;
|
|
721
|
-
background: rgba(255, 255, 255, 0.9);
|
|
722
|
-
color: #253041;
|
|
723
|
-
font-size: 0.72rem;
|
|
724
|
-
font-weight: 850;
|
|
725
|
-
cursor: pointer;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
.custom-inpaint-history-check[disabled] {
|
|
729
|
-
cursor: wait;
|
|
730
|
-
opacity: 0.64;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
.custom-inpaint-history-icon svg {
|
|
734
|
-
width: 13px;
|
|
735
|
-
height: 13px;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
.custom-inpaint-history-popconfirm {
|
|
739
|
-
position: absolute;
|
|
740
|
-
right: 6px;
|
|
741
|
-
top: 6px;
|
|
742
|
-
z-index: 6;
|
|
743
|
-
width: 156px;
|
|
744
|
-
display: grid;
|
|
745
|
-
gap: 7px;
|
|
746
|
-
padding: 8px;
|
|
747
|
-
border: 1px solid rgba(191, 164, 109, 0.34);
|
|
748
|
-
border-radius: 12px;
|
|
749
|
-
background: rgba(255, 251, 244, 0.98);
|
|
750
|
-
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.16);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
.custom-inpaint-history-popconfirm[hidden] {
|
|
754
|
-
display: none;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
.custom-inpaint-history-popconfirm p {
|
|
758
|
-
margin: 0;
|
|
759
|
-
color: #253041;
|
|
760
|
-
font-size: 0.76rem;
|
|
761
|
-
font-weight: 800;
|
|
762
|
-
line-height: 1.25;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
.custom-inpaint-history-popconfirm-actions {
|
|
766
|
-
display: flex;
|
|
767
|
-
gap: 5px;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
.custom-inpaint-history-popconfirm-actions button {
|
|
771
|
-
flex: 1 1 0;
|
|
772
|
-
min-height: 26px;
|
|
773
|
-
padding: 0 7px;
|
|
774
|
-
border-radius: 9px;
|
|
775
|
-
border: 1px solid rgba(191, 164, 109, 0.28);
|
|
776
|
-
background: rgba(255, 255, 255, 0.92);
|
|
777
|
-
color: #253041;
|
|
778
|
-
font: inherit;
|
|
779
|
-
font-size: 0.72rem;
|
|
780
|
-
font-weight: 800;
|
|
781
|
-
cursor: pointer;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
.custom-inpaint-history-popconfirm-actions button[data-danger="true"] {
|
|
785
|
-
border-color: rgba(155, 45, 45, 0.3);
|
|
786
|
-
color: #9b2d2d;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
.custom-inpaint-status[data-tone="error"] {
|
|
790
|
-
color: #9b2d2d;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.custom-inpaint-status[data-tone="success"] {
|
|
794
|
-
color: #2f7d4f;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
.custom-inpaint-lightbox {
|
|
798
|
-
position: fixed;
|
|
799
|
-
inset: 0;
|
|
800
|
-
z-index: 80;
|
|
801
|
-
display: none;
|
|
802
|
-
place-items: center;
|
|
803
|
-
padding: 22px;
|
|
804
|
-
background: rgba(15, 23, 42, 0.72);
|
|
805
|
-
backdrop-filter: blur(12px);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
.custom-inpaint-lightbox[data-open="true"] {
|
|
809
|
-
display: grid;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
.custom-inpaint-lightbox[hidden] {
|
|
813
|
-
display: none;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
.custom-inpaint-lightbox-dialog {
|
|
817
|
-
width: min(1100px, 96vw);
|
|
818
|
-
max-height: 94vh;
|
|
819
|
-
display: grid;
|
|
820
|
-
grid-template-rows: auto minmax(0, 1fr);
|
|
821
|
-
gap: 12px;
|
|
822
|
-
padding: 12px;
|
|
823
|
-
border: 1px solid rgba(255, 251, 244, 0.18);
|
|
824
|
-
border-radius: 18px;
|
|
825
|
-
background: rgba(37, 45, 61, 0.96);
|
|
826
|
-
box-shadow: 0 24px 72px rgba(0, 0, 0, 0.38);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
.custom-inpaint-lightbox-head {
|
|
830
|
-
display: flex;
|
|
831
|
-
align-items: center;
|
|
832
|
-
justify-content: space-between;
|
|
833
|
-
gap: 10px;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.custom-inpaint-lightbox-title {
|
|
837
|
-
min-width: 0;
|
|
838
|
-
margin: 0;
|
|
839
|
-
color: #fffaf0;
|
|
840
|
-
font-size: 0.9rem;
|
|
841
|
-
font-weight: 800;
|
|
842
|
-
overflow: hidden;
|
|
843
|
-
text-overflow: ellipsis;
|
|
844
|
-
white-space: nowrap;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
.custom-inpaint-lightbox-actions {
|
|
848
|
-
display: flex;
|
|
849
|
-
align-items: center;
|
|
850
|
-
gap: 8px;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
.custom-inpaint-lightbox-stage {
|
|
854
|
-
min-height: 0;
|
|
855
|
-
display: grid;
|
|
856
|
-
place-items: center;
|
|
857
|
-
overflow: auto;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
.custom-inpaint-lightbox-stage img {
|
|
861
|
-
max-width: 100%;
|
|
862
|
-
max-height: calc(94vh - 92px);
|
|
863
|
-
object-fit: contain;
|
|
864
|
-
border-radius: 12px;
|
|
865
|
-
background: #111827;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
@media (max-width: 1100px) {
|
|
869
|
-
.custom-inpaint-workbench {
|
|
870
|
-
grid-template-columns: 1fr;
|
|
871
|
-
overflow: auto;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.editor-inpaint-panel {
|
|
875
|
-
max-height: none;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
`,
|
|
879
|
-
body: `
|
|
880
|
-
<section class="frame custom-inpaint-frame">
|
|
881
|
-
<header class="topbar">
|
|
882
|
-
${renderBrandLockup({ displayName: input.account.name, email: input.account.email, accountId: input.account.userId })}
|
|
883
|
-
<div class="topbar-actions">
|
|
884
|
-
${renderPrimaryNav({ loggedIn: true, current: "inpaint", accountId: input.account.userId })}
|
|
885
|
-
</div>
|
|
886
|
-
</header>
|
|
887
|
-
<div class="frame-body custom-inpaint-shell">
|
|
888
|
-
<section class="custom-inpaint-workbench">
|
|
889
|
-
<aside class="custom-inpaint-panel">
|
|
890
|
-
<div class="custom-inpaint-panel-head">
|
|
891
|
-
<h1>Inpaint</h1>
|
|
892
|
-
<button type="button" class="editor-history-button" data-custom-inpaint-start-new>Start New</button>
|
|
893
|
-
</div>
|
|
894
|
-
<p>Upload a source image, paint replacement regions, then create a reusable image edit for a timeline layer.</p>
|
|
895
|
-
<button type="button" class="custom-inpaint-upload" data-custom-inpaint-upload>
|
|
896
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14V4"></path><path d="m6.5 7.5 3.5-3.5 3.5 3.5"></path><path d="M4 15.8h12"></path></svg>
|
|
897
|
-
<strong>Add Source Image</strong>
|
|
898
|
-
<span data-custom-inpaint-source-name>No image selected</span>
|
|
899
|
-
</button>
|
|
900
|
-
<input data-custom-inpaint-file type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" hidden />
|
|
901
|
-
<p class="custom-inpaint-status" data-custom-inpaint-status aria-live="polite"></p>
|
|
902
|
-
<div class="custom-inpaint-output" data-custom-inpaint-output hidden>
|
|
903
|
-
<h2>Result</h2>
|
|
904
|
-
<button type="button" class="custom-inpaint-preview" data-custom-inpaint-result-open aria-label="View inpaint result">
|
|
905
|
-
<img data-custom-inpaint-result alt="Image edit result" />
|
|
906
|
-
</button>
|
|
907
|
-
<a class="editor-history-button is-primary" data-custom-inpaint-download href="#" target="_blank" rel="noopener noreferrer" download>Download</a>
|
|
908
|
-
</div>
|
|
909
|
-
<section class="custom-inpaint-history" aria-label="Image edit job history">
|
|
910
|
-
<div class="custom-inpaint-history-head">
|
|
911
|
-
<h2>History</h2>
|
|
912
|
-
<button type="button" class="editor-history-button custom-inpaint-history-refresh" data-custom-inpaint-history-refresh aria-label="Refresh image edit history" title="Refresh history">
|
|
913
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16.5 10a6.5 6.5 0 1 1-1.9-4.6"></path><path d="M16.5 4.2v4.2h-4.2"></path></svg>
|
|
914
|
-
</button>
|
|
915
|
-
</div>
|
|
916
|
-
<div class="custom-inpaint-history-list" data-custom-inpaint-history-list>
|
|
917
|
-
<div class="custom-inpaint-history-empty">Loading history...</div>
|
|
918
|
-
</div>
|
|
919
|
-
</section>
|
|
920
|
-
</aside>
|
|
921
|
-
<main class="custom-inpaint-stage">
|
|
922
|
-
<div class="custom-inpaint-empty" data-custom-inpaint-empty>Upload an image to start painting</div>
|
|
923
|
-
<div class="editor-inpaint-canvas-wrap" data-custom-inpaint-canvas-wrap hidden>
|
|
924
|
-
<img data-editor-inpaint-image alt="" />
|
|
925
|
-
<canvas data-editor-inpaint-canvas></canvas>
|
|
926
|
-
<div class="editor-inpaint-brush-cursor" data-editor-inpaint-brush-cursor aria-hidden="true"></div>
|
|
927
|
-
</div>
|
|
928
|
-
</main>
|
|
929
|
-
<aside class="editor-inpaint-panel" aria-label="Image edit controls">
|
|
930
|
-
<div class="custom-inpaint-tool-head">
|
|
931
|
-
<div class="custom-inpaint-actions" aria-label="Mask history">
|
|
932
|
-
<button type="button" class="editor-history-button is-icon" data-editor-inpaint-undo aria-label="Undo paint stroke" title="Undo">
|
|
933
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.2 7H3.8V3.6"></path><path d="M4 7a7 7 0 1 1 1.9 6.6"></path></svg>
|
|
934
|
-
</button>
|
|
935
|
-
<button type="button" class="editor-history-button is-icon" data-editor-inpaint-redo aria-label="Redo paint stroke" title="Redo">
|
|
936
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.8 7h3.4V3.6"></path><path d="M16 7a7 7 0 1 0-1.9 6.6"></path></svg>
|
|
937
|
-
</button>
|
|
938
|
-
</div>
|
|
939
|
-
<div class="custom-inpaint-settings-wrap">
|
|
940
|
-
<button type="button" class="editor-history-button is-icon" data-custom-inpaint-settings-button aria-label="Image edit settings" title="Settings">
|
|
941
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.8 3.4 9.4 2h1.2l.6 1.4 1.2.5 1.4-.6.9.9-.6 1.4.5 1.2 1.4.6v1.2l-1.4.6-.5 1.2.6 1.4-.9.9-1.4-.6-1.2.5-.6 1.4H9.4l-.6-1.4-1.2-.5-1.4.6-.9-.9.6-1.4-.5-1.2L4 8.6V7.4l1.4-.6.5-1.2-.6-1.4.9-.9 1.4.6 1.2-.5Z"></path><circle cx="10" cy="8" r="2.4"></circle></svg>
|
|
942
|
-
</button>
|
|
943
|
-
<div class="custom-inpaint-settings" data-custom-inpaint-settings hidden>
|
|
944
|
-
<label>
|
|
945
|
-
<span class="editor-inpaint-label">Tracer</span>
|
|
946
|
-
<input data-custom-inpaint-tracer type="text" autocomplete="off" spellcheck="false" placeholder="image-edit" />
|
|
947
|
-
</label>
|
|
948
|
-
</div>
|
|
949
|
-
</div>
|
|
950
|
-
</div>
|
|
951
|
-
<div class="editor-inpaint-toolbar">
|
|
952
|
-
<label class="editor-inpaint-brush">
|
|
953
|
-
<span class="editor-inpaint-label">Brush <span data-editor-inpaint-brush-label>70</span></span>
|
|
954
|
-
<input data-editor-inpaint-brush type="range" min="6" max="180" step="1" value="70" />
|
|
955
|
-
</label>
|
|
956
|
-
<input class="editor-inpaint-color-picker" data-editor-inpaint-color-picker type="color" value="#ff2d55" aria-label="Mask color" />
|
|
957
|
-
<label class="editor-inpaint-provider" data-editor-inpaint-provider-wrap>
|
|
958
|
-
<span class="editor-inpaint-label">Provider</span>
|
|
959
|
-
<select data-editor-inpaint-provider aria-label="AI provider"></select>
|
|
960
|
-
</label>
|
|
961
|
-
</div>
|
|
962
|
-
<div class="editor-inpaint-global">
|
|
963
|
-
<span class="editor-inpaint-label">Whole image prompt</span>
|
|
964
|
-
<div class="editor-inpaint-prompt-field">
|
|
965
|
-
<textarea class="editor-inpaint-prompt" data-editor-inpaint-global-prompt placeholder="Describe the overall edit, style, constraints, or preservation notes."></textarea>
|
|
966
|
-
<button type="button" class="editor-inpaint-attach-button" data-editor-inpaint-attach-scope="global" aria-label="Attach reference image" title="Attach reference image">
|
|
967
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8.1 10.7 4.8-4.8a2.5 2.5 0 0 1 3.5 3.5l-6.1 6.1a4 4 0 0 1-5.7-5.7l6.3-6.3"></path><path d="m10 8.8-4.1 4.1a1.6 1.6 0 0 0 2.3 2.3l5.6-5.6"></path></svg>
|
|
968
|
-
</button>
|
|
969
|
-
</div>
|
|
970
|
-
<div class="editor-inpaint-attachment-tools">
|
|
971
|
-
<span class="editor-inpaint-attachment-chips" data-editor-inpaint-attachments="global"></span>
|
|
972
|
-
</div>
|
|
973
|
-
</div>
|
|
974
|
-
<input data-editor-inpaint-attachment-input type="file" accept="image/jpeg,image/png,image/webp" multiple hidden />
|
|
975
|
-
<div class="editor-inpaint-swatches" data-editor-inpaint-swatches aria-label="Mask colors"></div>
|
|
976
|
-
<div class="editor-inpaint-prompts" data-editor-inpaint-prompts></div>
|
|
977
|
-
<button type="button" class="editor-history-button is-block" data-editor-inpaint-add-color>Add color</button>
|
|
978
|
-
<div class="custom-inpaint-actions">
|
|
979
|
-
<button type="button" class="editor-history-button" data-editor-inpaint-clear>Clear</button>
|
|
980
|
-
<button type="button" class="editor-history-button is-primary" data-editor-inpaint-submit><span data-editor-inpaint-submit-label>Create image edit</span></button>
|
|
981
|
-
</div>
|
|
982
|
-
</aside>
|
|
983
|
-
</section>
|
|
984
|
-
</div>
|
|
985
|
-
</section>
|
|
986
|
-
<div class="custom-inpaint-lightbox" data-custom-inpaint-lightbox data-open="false" hidden>
|
|
987
|
-
<div class="custom-inpaint-lightbox-dialog" role="dialog" aria-modal="true" aria-labelledby="custom-inpaint-lightbox-title">
|
|
988
|
-
<div class="custom-inpaint-lightbox-head">
|
|
989
|
-
<p class="custom-inpaint-lightbox-title" id="custom-inpaint-lightbox-title" data-custom-inpaint-lightbox-title>Image edit output</p>
|
|
990
|
-
<div class="custom-inpaint-lightbox-actions">
|
|
991
|
-
<button type="button" class="editor-history-button is-icon" data-custom-inpaint-lightbox-copy aria-label="Copy image URL" title="Copy URL">
|
|
992
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.4 11.6a3.2 3.2 0 0 0 4.5 0l2.2-2.2a3.2 3.2 0 0 0-4.5-4.5L9.4 6.1"></path><path d="M11.6 8.4a3.2 3.2 0 0 0-4.5 0L4.9 10.6a3.2 3.2 0 0 0 4.5 4.5l1.2-1.2"></path></svg>
|
|
993
|
-
</button>
|
|
994
|
-
<a class="editor-history-button is-primary" data-custom-inpaint-lightbox-download href="#" target="_blank" rel="noopener noreferrer" download>Download</a>
|
|
995
|
-
<button type="button" class="editor-history-button is-icon" data-custom-inpaint-lightbox-close aria-label="Close preview" title="Close">
|
|
996
|
-
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 5l10 10"></path><path d="M15 5 5 15"></path></svg>
|
|
997
|
-
</button>
|
|
998
|
-
</div>
|
|
999
|
-
</div>
|
|
1000
|
-
<div class="custom-inpaint-lightbox-stage">
|
|
1001
|
-
<img data-custom-inpaint-lightbox-image alt="Image edit output preview" />
|
|
1002
|
-
</div>
|
|
1003
|
-
</div>
|
|
1004
|
-
</div>
|
|
1005
|
-
<script>
|
|
1006
|
-
(() => {
|
|
1007
|
-
const uploadButton = document.querySelector("[data-custom-inpaint-upload]");
|
|
1008
|
-
const startNewButton = document.querySelector("[data-custom-inpaint-start-new]");
|
|
1009
|
-
const fileInput = document.querySelector("[data-custom-inpaint-file]");
|
|
1010
|
-
const sourceName = document.querySelector("[data-custom-inpaint-source-name]");
|
|
1011
|
-
const statusEl = document.querySelector("[data-custom-inpaint-status]");
|
|
1012
|
-
const emptyState = document.querySelector("[data-custom-inpaint-empty]");
|
|
1013
|
-
const canvasWrap = document.querySelector("[data-custom-inpaint-canvas-wrap]");
|
|
1014
|
-
const outputPanel = document.querySelector("[data-custom-inpaint-output]");
|
|
1015
|
-
const resultOpenButton = document.querySelector("[data-custom-inpaint-result-open]");
|
|
1016
|
-
const resultImage = document.querySelector("[data-custom-inpaint-result]");
|
|
1017
|
-
const resultDownload = document.querySelector("[data-custom-inpaint-download]");
|
|
1018
|
-
const historyList = document.querySelector("[data-custom-inpaint-history-list]");
|
|
1019
|
-
const historyRefresh = document.querySelector("[data-custom-inpaint-history-refresh]");
|
|
1020
|
-
const inpaintImage = document.querySelector("[data-editor-inpaint-image]");
|
|
1021
|
-
const inpaintCanvas = document.querySelector("[data-editor-inpaint-canvas]");
|
|
1022
|
-
const inpaintBrushCursor = document.querySelector("[data-editor-inpaint-brush-cursor]");
|
|
1023
|
-
const inpaintUndo = document.querySelector("[data-editor-inpaint-undo]");
|
|
1024
|
-
const inpaintRedo = document.querySelector("[data-editor-inpaint-redo]");
|
|
1025
|
-
const inpaintClear = document.querySelector("[data-editor-inpaint-clear]");
|
|
1026
|
-
const inpaintBrush = document.querySelector("[data-editor-inpaint-brush]");
|
|
1027
|
-
const inpaintBrushLabel = document.querySelector("[data-editor-inpaint-brush-label]");
|
|
1028
|
-
const inpaintColorPicker = document.querySelector("[data-editor-inpaint-color-picker]");
|
|
1029
|
-
const inpaintAddColor = document.querySelector("[data-editor-inpaint-add-color]");
|
|
1030
|
-
const inpaintProviderWrap = document.querySelector("[data-editor-inpaint-provider-wrap]");
|
|
1031
|
-
const inpaintProviderSelect = document.querySelector("[data-editor-inpaint-provider]");
|
|
1032
|
-
const inpaintGlobalPrompt = document.querySelector("[data-editor-inpaint-global-prompt]");
|
|
1033
|
-
const inpaintAttachmentInput = document.querySelector("[data-editor-inpaint-attachment-input]");
|
|
1034
|
-
const inpaintGlobalAttachments = document.querySelector('[data-editor-inpaint-attachments="global"]');
|
|
1035
|
-
const inpaintSwatches = document.querySelector("[data-editor-inpaint-swatches]");
|
|
1036
|
-
const inpaintPrompts = document.querySelector("[data-editor-inpaint-prompts]");
|
|
1037
|
-
const inpaintSubmit = document.querySelector("[data-editor-inpaint-submit]");
|
|
1038
|
-
const inpaintSubmitLabel = document.querySelector("[data-editor-inpaint-submit-label]");
|
|
1039
|
-
const settingsButton = document.querySelector("[data-custom-inpaint-settings-button]");
|
|
1040
|
-
const settingsPopover = document.querySelector("[data-custom-inpaint-settings]");
|
|
1041
|
-
const tracerInput = document.querySelector("[data-custom-inpaint-tracer]");
|
|
1042
|
-
const lightbox = document.querySelector("[data-custom-inpaint-lightbox]");
|
|
1043
|
-
const lightboxTitle = document.querySelector("[data-custom-inpaint-lightbox-title]");
|
|
1044
|
-
const lightboxImage = document.querySelector("[data-custom-inpaint-lightbox-image]");
|
|
1045
|
-
const lightboxCopy = document.querySelector("[data-custom-inpaint-lightbox-copy]");
|
|
1046
|
-
const lightboxDownload = document.querySelector("[data-custom-inpaint-lightbox-download]");
|
|
1047
|
-
const lightboxClose = document.querySelector("[data-custom-inpaint-lightbox-close]");
|
|
1048
|
-
if (!(uploadButton instanceof HTMLButtonElement)
|
|
1049
|
-
|| !(startNewButton instanceof HTMLButtonElement)
|
|
1050
|
-
|| !(fileInput instanceof HTMLInputElement)
|
|
1051
|
-
|| !(sourceName instanceof HTMLElement)
|
|
1052
|
-
|| !(statusEl instanceof HTMLElement)
|
|
1053
|
-
|| !(emptyState instanceof HTMLElement)
|
|
1054
|
-
|| !(canvasWrap instanceof HTMLElement)
|
|
1055
|
-
|| !(outputPanel instanceof HTMLElement)
|
|
1056
|
-
|| !(resultOpenButton instanceof HTMLButtonElement)
|
|
1057
|
-
|| !(resultImage instanceof HTMLImageElement)
|
|
1058
|
-
|| !(resultDownload instanceof HTMLAnchorElement)
|
|
1059
|
-
|| !(historyList instanceof HTMLElement)
|
|
1060
|
-
|| !(historyRefresh instanceof HTMLButtonElement)
|
|
1061
|
-
|| !(inpaintImage instanceof HTMLImageElement)
|
|
1062
|
-
|| !(inpaintCanvas instanceof HTMLCanvasElement)
|
|
1063
|
-
|| !(inpaintBrushCursor instanceof HTMLElement)
|
|
1064
|
-
|| !(inpaintUndo instanceof HTMLButtonElement)
|
|
1065
|
-
|| !(inpaintRedo instanceof HTMLButtonElement)
|
|
1066
|
-
|| !(inpaintClear instanceof HTMLButtonElement)
|
|
1067
|
-
|| !(inpaintBrush instanceof HTMLInputElement)
|
|
1068
|
-
|| !(inpaintBrushLabel instanceof HTMLElement)
|
|
1069
|
-
|| !(inpaintColorPicker instanceof HTMLInputElement)
|
|
1070
|
-
|| !(inpaintAddColor instanceof HTMLButtonElement)
|
|
1071
|
-
|| !(inpaintProviderWrap instanceof HTMLElement)
|
|
1072
|
-
|| !(inpaintProviderSelect instanceof HTMLSelectElement)
|
|
1073
|
-
|| !(inpaintGlobalPrompt instanceof HTMLTextAreaElement)
|
|
1074
|
-
|| !(inpaintAttachmentInput instanceof HTMLInputElement)
|
|
1075
|
-
|| !(inpaintGlobalAttachments instanceof HTMLElement)
|
|
1076
|
-
|| !(inpaintSwatches instanceof HTMLElement)
|
|
1077
|
-
|| !(inpaintPrompts instanceof HTMLElement)
|
|
1078
|
-
|| !(inpaintSubmit instanceof HTMLButtonElement)
|
|
1079
|
-
|| !(inpaintSubmitLabel instanceof HTMLElement)
|
|
1080
|
-
|| !(settingsButton instanceof HTMLButtonElement)
|
|
1081
|
-
|| !(settingsPopover instanceof HTMLElement)
|
|
1082
|
-
|| !(tracerInput instanceof HTMLInputElement)
|
|
1083
|
-
|| !(lightbox instanceof HTMLElement)
|
|
1084
|
-
|| !(lightboxTitle instanceof HTMLElement)
|
|
1085
|
-
|| !(lightboxImage instanceof HTMLImageElement)
|
|
1086
|
-
|| !(lightboxCopy instanceof HTMLButtonElement)
|
|
1087
|
-
|| !(lightboxDownload instanceof HTMLAnchorElement)
|
|
1088
|
-
|| !(lightboxClose instanceof HTMLButtonElement)) {
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
let sourceImageUrl = "";
|
|
1093
|
-
let sourceImageName = "";
|
|
1094
|
-
let inpaintMaskColors = [{ color: "#ff2d55", prompt: "" }];
|
|
1095
|
-
let activeInpaintColor = "#ff2d55";
|
|
1096
|
-
let inpaintStrokes = [];
|
|
1097
|
-
let redoStrokes = [];
|
|
1098
|
-
let currentInpaintStroke = null;
|
|
1099
|
-
let isInpaintPainting = false;
|
|
1100
|
-
let inpaintAttachments = [];
|
|
1101
|
-
let pendingInpaintAttachmentScope = null;
|
|
1102
|
-
let inpaintHistoryItems = [];
|
|
1103
|
-
let historyTracerRefreshTimer = 0;
|
|
1104
|
-
const hiddenInpaintHistoryKeys = new Set();
|
|
1105
|
-
const maxInpaintAttachments = 8;
|
|
1106
|
-
const allowedInpaintAttachmentTypes = new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
1107
|
-
|
|
1108
|
-
function makeTracerId() {
|
|
1109
|
-
return "custom-inpaint-" + (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function"
|
|
1110
|
-
? globalThis.crypto.randomUUID().replace(/-/g, "").slice(0, 8)
|
|
1111
|
-
: Math.random().toString(16).slice(2, 10).padEnd(8, "0").slice(0, 8));
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
function getTracerValue() {
|
|
1115
|
-
const value = tracerInput.value.trim();
|
|
1116
|
-
if (value) return value;
|
|
1117
|
-
const tracer = makeTracerId();
|
|
1118
|
-
tracerInput.value = tracer;
|
|
1119
|
-
return tracer;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
function queueInpaintHistoryReload() {
|
|
1123
|
-
window.clearTimeout(historyTracerRefreshTimer);
|
|
1124
|
-
historyTracerRefreshTimer = window.setTimeout(() => {
|
|
1125
|
-
void loadInpaintHistory();
|
|
1126
|
-
}, 300);
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
function setSubmitLoading(isLoading) {
|
|
1130
|
-
inpaintSubmit.disabled = isLoading;
|
|
1131
|
-
inpaintSubmit.setAttribute("data-loading", isLoading ? "true" : "false");
|
|
1132
|
-
inpaintSubmitLabel.textContent = isLoading ? "Creating" : "Create image edit";
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
function setStatus(message, tone) {
|
|
1136
|
-
statusEl.textContent = message || "";
|
|
1137
|
-
if (tone) {
|
|
1138
|
-
statusEl.setAttribute("data-tone", tone);
|
|
1139
|
-
} else {
|
|
1140
|
-
statusEl.removeAttribute("data-tone");
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function imageDownloadName(prefix, createdAt) {
|
|
1145
|
-
const stamp = String(createdAt || new Date().toISOString()).replace(/[^0-9a-z]+/gi, "-");
|
|
1146
|
-
return prefix + "-" + stamp + ".png";
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
function openImagePreview(url, label, downloadName) {
|
|
1150
|
-
if (!url) return;
|
|
1151
|
-
lightboxImage.src = url;
|
|
1152
|
-
lightboxImage.alt = label || "Image edit output preview";
|
|
1153
|
-
lightboxTitle.textContent = label || "Image edit output";
|
|
1154
|
-
lightboxCopy.dataset.copyUrl = url;
|
|
1155
|
-
lightboxCopy.setAttribute("title", "Copy URL");
|
|
1156
|
-
lightboxDownload.href = url;
|
|
1157
|
-
lightboxDownload.setAttribute("download", downloadName || imageDownloadName("inpaint-output"));
|
|
1158
|
-
lightbox.hidden = false;
|
|
1159
|
-
lightbox.setAttribute("data-open", "true");
|
|
1160
|
-
document.body.style.overflow = "hidden";
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
function closeImagePreview() {
|
|
1164
|
-
lightbox.setAttribute("data-open", "false");
|
|
1165
|
-
lightbox.hidden = true;
|
|
1166
|
-
lightboxImage.removeAttribute("src");
|
|
1167
|
-
lightboxCopy.dataset.copyUrl = "";
|
|
1168
|
-
lightboxCopy.setAttribute("title", "Copy URL");
|
|
1169
|
-
lightboxDownload.href = "#";
|
|
1170
|
-
document.body.style.overflow = "";
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
function updateBrushCursorSize() {
|
|
1174
|
-
const rect = inpaintCanvas.getBoundingClientRect();
|
|
1175
|
-
const brushSize = Math.max(1, Number(inpaintBrush.value) || 40);
|
|
1176
|
-
const displayScale = inpaintCanvas.width && rect.width ? rect.width / inpaintCanvas.width : 1;
|
|
1177
|
-
canvasWrap.style.setProperty("--inpaint-brush-cursor-size", Math.max(6, brushSize * displayScale) + "px");
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
function updateBrushCursorPosition(event) {
|
|
1181
|
-
const rect = inpaintCanvas.getBoundingClientRect();
|
|
1182
|
-
canvasWrap.style.setProperty("--inpaint-brush-cursor-x", Math.max(0, Math.min(rect.width, event.clientX - rect.left)) + "px");
|
|
1183
|
-
canvasWrap.style.setProperty("--inpaint-brush-cursor-y", Math.max(0, Math.min(rect.height, event.clientY - rect.top)) + "px");
|
|
1184
|
-
updateBrushCursorSize();
|
|
1185
|
-
inpaintBrushCursor.dataset.visible = "true";
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
function hideBrushCursor() {
|
|
1189
|
-
inpaintBrushCursor.dataset.visible = "false";
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
function normalizeHexColor(value) {
|
|
1193
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
1194
|
-
if (/^#[0-9a-f]{6}$/i.test(raw)) return raw;
|
|
1195
|
-
if (/^#[0-9a-f]{3}$/i.test(raw)) {
|
|
1196
|
-
return "#" + raw.slice(1).split("").map((part) => part + part).join("");
|
|
1197
|
-
}
|
|
1198
|
-
return "#ff2d55";
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
const inpaintColorPalette = ["#ff2d55", "#3b82f6", "#22c55e", "#f59e0b", "#a855f7", "#06b6d4", "#f97316", "#ec4899"];
|
|
1202
|
-
|
|
1203
|
-
function syncInpaintPromptValuesFromDom() {
|
|
1204
|
-
const fields = Array.from(inpaintPrompts.querySelectorAll("[data-color]"));
|
|
1205
|
-
for (const field of fields) {
|
|
1206
|
-
if (field instanceof HTMLTextAreaElement) {
|
|
1207
|
-
updateInpaintPrompt(field.dataset.color || "", field.value);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
function attachmentId() {
|
|
1213
|
-
return "att_" + Date.now().toString(36) + "_" + Math.random().toString(16).slice(2, 8);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
function attachmentsForScope(scope, color) {
|
|
1217
|
-
const normalizedColor = color ? normalizeHexColor(color) : "";
|
|
1218
|
-
return inpaintAttachments.filter((attachment) => (
|
|
1219
|
-
attachment.scope === scope && (!normalizedColor || attachment.color === normalizedColor)
|
|
1220
|
-
));
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
function renderAttachmentChips(container, scope, color) {
|
|
1224
|
-
container.innerHTML = "";
|
|
1225
|
-
for (const attachment of attachmentsForScope(scope, color)) {
|
|
1226
|
-
const chip = document.createElement("span");
|
|
1227
|
-
chip.className = "editor-inpaint-attachment-chip";
|
|
1228
|
-
const preview = document.createElement("img");
|
|
1229
|
-
preview.src = attachment.url;
|
|
1230
|
-
preview.alt = "";
|
|
1231
|
-
preview.loading = "lazy";
|
|
1232
|
-
const label = document.createElement("span");
|
|
1233
|
-
label.textContent = attachment.fileName || "reference image";
|
|
1234
|
-
const remove = document.createElement("button");
|
|
1235
|
-
remove.type = "button";
|
|
1236
|
-
remove.textContent = "×";
|
|
1237
|
-
remove.dataset.editorInpaintRemoveAttachment = attachment.id;
|
|
1238
|
-
remove.setAttribute("aria-label", "Remove " + (attachment.fileName || "reference image"));
|
|
1239
|
-
chip.append(preview, label, remove);
|
|
1240
|
-
container.append(chip);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
function renderAllAttachmentChips() {
|
|
1245
|
-
renderAttachmentChips(inpaintGlobalAttachments, "global");
|
|
1246
|
-
for (const container of Array.from(inpaintPrompts.querySelectorAll("[data-editor-inpaint-attachments]"))) {
|
|
1247
|
-
if (container instanceof HTMLElement && container.dataset.color) {
|
|
1248
|
-
renderAttachmentChips(container, "color", container.dataset.color);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
function openAttachmentPicker(scope, color) {
|
|
1254
|
-
pendingInpaintAttachmentScope = { scope, color: color ? normalizeHexColor(color) : "" };
|
|
1255
|
-
inpaintAttachmentInput.click();
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
function isAllowedInpaintAttachment(file) {
|
|
1259
|
-
return file instanceof File && allowedInpaintAttachmentTypes.has(file.type);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
function imageFilesFromPaste(event) {
|
|
1263
|
-
const clipboard = event.clipboardData;
|
|
1264
|
-
if (!clipboard) return [];
|
|
1265
|
-
const files = [];
|
|
1266
|
-
for (const item of Array.from(clipboard.items || [])) {
|
|
1267
|
-
if (item.kind !== "file") continue;
|
|
1268
|
-
const file = item.getAsFile();
|
|
1269
|
-
if (file) files.push(file);
|
|
1270
|
-
}
|
|
1271
|
-
if (!files.length) {
|
|
1272
|
-
for (const file of Array.from(clipboard.files || [])) {
|
|
1273
|
-
files.push(file);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
return files;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function handleAttachmentPaste(event, scope, color) {
|
|
1280
|
-
const files = imageFilesFromPaste(event);
|
|
1281
|
-
if (!files.length) return;
|
|
1282
|
-
event.preventDefault();
|
|
1283
|
-
const allowed = files.filter(isAllowedInpaintAttachment);
|
|
1284
|
-
if (!allowed.length) {
|
|
1285
|
-
setStatus("Reference attachments must be JPEG, PNG, or WebP images.", "error");
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
void addAttachmentFilesForScope(allowed, {
|
|
1289
|
-
scope,
|
|
1290
|
-
color: scope === "color" ? normalizeHexColor(color) : ""
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
function pickNextInpaintColor(requestedColor) {
|
|
1295
|
-
const requested = normalizeHexColor(requestedColor);
|
|
1296
|
-
const used = new Set(inpaintMaskColors.map((entry) => entry.color));
|
|
1297
|
-
if (!used.has(requested)) {
|
|
1298
|
-
return requested;
|
|
1299
|
-
}
|
|
1300
|
-
for (const color of inpaintColorPalette) {
|
|
1301
|
-
if (!used.has(color)) {
|
|
1302
|
-
return color;
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
for (let index = 0; index < 32; index += 1) {
|
|
1306
|
-
const color = "#" + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, "0");
|
|
1307
|
-
if (!used.has(color)) {
|
|
1308
|
-
return color;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
return requested;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
function renderInpaintControls() {
|
|
1315
|
-
syncInpaintPromptValuesFromDom();
|
|
1316
|
-
inpaintBrushLabel.textContent = inpaintBrush.value;
|
|
1317
|
-
inpaintSwatches.innerHTML = "";
|
|
1318
|
-
inpaintPrompts.innerHTML = "";
|
|
1319
|
-
for (const entry of inpaintMaskColors) {
|
|
1320
|
-
const row = document.createElement("div");
|
|
1321
|
-
row.className = "editor-inpaint-color-row";
|
|
1322
|
-
row.dataset.color = entry.color;
|
|
1323
|
-
row.dataset.active = entry.color === activeInpaintColor ? "true" : "false";
|
|
1324
|
-
const dot = document.createElement("button");
|
|
1325
|
-
dot.type = "button";
|
|
1326
|
-
dot.className = "editor-inpaint-color-dot";
|
|
1327
|
-
dot.style.setProperty("--inpaint-color", entry.color);
|
|
1328
|
-
dot.dataset.color = entry.color;
|
|
1329
|
-
dot.setAttribute("aria-label", "Use mask color " + entry.color);
|
|
1330
|
-
dot.setAttribute("title", entry.color);
|
|
1331
|
-
const colorTools = document.createElement("div");
|
|
1332
|
-
colorTools.className = "editor-inpaint-color-tools";
|
|
1333
|
-
colorTools.append(dot);
|
|
1334
|
-
if (inpaintMaskColors.length > 1) {
|
|
1335
|
-
const removeColor = document.createElement("button");
|
|
1336
|
-
removeColor.type = "button";
|
|
1337
|
-
removeColor.className = "editor-inpaint-color-remove";
|
|
1338
|
-
removeColor.dataset.editorInpaintRemoveColor = entry.color;
|
|
1339
|
-
removeColor.setAttribute("aria-label", "Remove mask color " + entry.color);
|
|
1340
|
-
removeColor.setAttribute("title", "Remove color");
|
|
1341
|
-
removeColor.innerHTML = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 5l10 10"></path><path d="M15 5 5 15"></path></svg>';
|
|
1342
|
-
removeColor.addEventListener("click", (event) => {
|
|
1343
|
-
event.preventDefault();
|
|
1344
|
-
event.stopPropagation();
|
|
1345
|
-
removeInpaintColor(entry.color);
|
|
1346
|
-
});
|
|
1347
|
-
colorTools.append(removeColor);
|
|
1348
|
-
}
|
|
1349
|
-
const prompt = document.createElement("textarea");
|
|
1350
|
-
prompt.className = "editor-inpaint-prompt";
|
|
1351
|
-
prompt.dataset.color = entry.color;
|
|
1352
|
-
prompt.value = entry.prompt || "";
|
|
1353
|
-
prompt.placeholder = "In color " + entry.color + " replace with...";
|
|
1354
|
-
const stack = document.createElement("div");
|
|
1355
|
-
stack.className = "editor-inpaint-prompt-stack";
|
|
1356
|
-
const field = document.createElement("div");
|
|
1357
|
-
field.className = "editor-inpaint-prompt-field";
|
|
1358
|
-
const tools = document.createElement("div");
|
|
1359
|
-
tools.className = "editor-inpaint-attachment-tools";
|
|
1360
|
-
const attach = document.createElement("button");
|
|
1361
|
-
attach.type = "button";
|
|
1362
|
-
attach.className = "editor-inpaint-attach-button";
|
|
1363
|
-
attach.dataset.editorInpaintAttachScope = "color";
|
|
1364
|
-
attach.dataset.color = entry.color;
|
|
1365
|
-
attach.setAttribute("aria-label", "Attach reference image for " + entry.color);
|
|
1366
|
-
attach.setAttribute("title", "Attach reference image");
|
|
1367
|
-
attach.innerHTML = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8.1 10.7 4.8-4.8a2.5 2.5 0 0 1 3.5 3.5l-6.1 6.1a4 4 0 0 1-5.7-5.7l6.3-6.3"></path><path d="m10 8.8-4.1 4.1a1.6 1.6 0 0 0 2.3 2.3l5.6-5.6"></path></svg>';
|
|
1368
|
-
const chips = document.createElement("span");
|
|
1369
|
-
chips.className = "editor-inpaint-attachment-chips";
|
|
1370
|
-
chips.dataset.editorInpaintAttachments = "color";
|
|
1371
|
-
chips.dataset.color = entry.color;
|
|
1372
|
-
field.append(prompt, attach);
|
|
1373
|
-
tools.append(chips);
|
|
1374
|
-
stack.append(field, tools);
|
|
1375
|
-
row.append(colorTools, stack);
|
|
1376
|
-
inpaintPrompts.append(row);
|
|
1377
|
-
}
|
|
1378
|
-
renderAllAttachmentChips();
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
function redrawInpaintMask() {
|
|
1382
|
-
const ctx = inpaintCanvas.getContext("2d");
|
|
1383
|
-
if (!ctx) return;
|
|
1384
|
-
ctx.clearRect(0, 0, inpaintCanvas.width, inpaintCanvas.height);
|
|
1385
|
-
ctx.lineCap = "round";
|
|
1386
|
-
ctx.lineJoin = "round";
|
|
1387
|
-
for (const stroke of inpaintStrokes) {
|
|
1388
|
-
if (!stroke.points || stroke.points.length < 1) continue;
|
|
1389
|
-
ctx.strokeStyle = stroke.color;
|
|
1390
|
-
ctx.lineWidth = stroke.size;
|
|
1391
|
-
ctx.globalAlpha = 1;
|
|
1392
|
-
ctx.beginPath();
|
|
1393
|
-
ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
|
|
1394
|
-
if (stroke.points.length === 1) {
|
|
1395
|
-
ctx.lineTo(stroke.points[0].x + 0.01, stroke.points[0].y + 0.01);
|
|
1396
|
-
} else {
|
|
1397
|
-
for (let index = 1; index < stroke.points.length; index += 1) {
|
|
1398
|
-
ctx.lineTo(stroke.points[index].x, stroke.points[index].y);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
ctx.stroke();
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
function syncInpaintCanvasSize() {
|
|
1406
|
-
if (!inpaintImage.naturalWidth || !inpaintImage.naturalHeight) return;
|
|
1407
|
-
if (inpaintCanvas.width !== inpaintImage.naturalWidth || inpaintCanvas.height !== inpaintImage.naturalHeight) {
|
|
1408
|
-
inpaintCanvas.width = inpaintImage.naturalWidth;
|
|
1409
|
-
inpaintCanvas.height = inpaintImage.naturalHeight;
|
|
1410
|
-
redrawInpaintMask();
|
|
1411
|
-
}
|
|
1412
|
-
updateBrushCursorSize();
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
function getInpaintPoint(event) {
|
|
1416
|
-
const rect = inpaintCanvas.getBoundingClientRect();
|
|
1417
|
-
return {
|
|
1418
|
-
x: Math.max(0, Math.min(inpaintCanvas.width, (event.clientX - rect.left) * (inpaintCanvas.width / rect.width))),
|
|
1419
|
-
y: Math.max(0, Math.min(inpaintCanvas.height, (event.clientY - rect.top) * (inpaintCanvas.height / rect.height)))
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
function beginInpaintStroke(event) {
|
|
1424
|
-
if (!sourceImageUrl || !inpaintCanvas.width || inpaintSubmit.disabled) return;
|
|
1425
|
-
event.preventDefault();
|
|
1426
|
-
inpaintCanvas.setPointerCapture(event.pointerId);
|
|
1427
|
-
isInpaintPainting = true;
|
|
1428
|
-
currentInpaintStroke = {
|
|
1429
|
-
color: activeInpaintColor,
|
|
1430
|
-
size: Number(inpaintBrush.value) || 40,
|
|
1431
|
-
points: [getInpaintPoint(event)]
|
|
1432
|
-
};
|
|
1433
|
-
inpaintStrokes.push(currentInpaintStroke);
|
|
1434
|
-
redoStrokes = [];
|
|
1435
|
-
redrawInpaintMask();
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
function continueInpaintStroke(event) {
|
|
1439
|
-
if (!isInpaintPainting || !currentInpaintStroke) return;
|
|
1440
|
-
event.preventDefault();
|
|
1441
|
-
currentInpaintStroke.points.push(getInpaintPoint(event));
|
|
1442
|
-
redrawInpaintMask();
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
function endInpaintStroke(event) {
|
|
1446
|
-
if (!isInpaintPainting) return;
|
|
1447
|
-
event.preventDefault();
|
|
1448
|
-
isInpaintPainting = false;
|
|
1449
|
-
currentInpaintStroke = null;
|
|
1450
|
-
try {
|
|
1451
|
-
inpaintCanvas.releasePointerCapture(event.pointerId);
|
|
1452
|
-
} catch {}
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
function addInpaintColor(color) {
|
|
1456
|
-
syncInpaintPromptValuesFromDom();
|
|
1457
|
-
const normalized = pickNextInpaintColor(color);
|
|
1458
|
-
if (!inpaintMaskColors.some((entry) => entry.color === normalized)) {
|
|
1459
|
-
inpaintMaskColors.push({ color: normalized, prompt: "" });
|
|
1460
|
-
}
|
|
1461
|
-
activeInpaintColor = normalized;
|
|
1462
|
-
inpaintColorPicker.value = normalized;
|
|
1463
|
-
renderInpaintControls();
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
function removeInpaintColor(color) {
|
|
1467
|
-
syncInpaintPromptValuesFromDom();
|
|
1468
|
-
const normalized = normalizeHexColor(color);
|
|
1469
|
-
if (inpaintMaskColors.length <= 1 || !inpaintMaskColors.some((entry) => entry.color === normalized)) {
|
|
1470
|
-
setStatus("Keep at least one mask color.", "error");
|
|
1471
|
-
return;
|
|
1472
|
-
}
|
|
1473
|
-
inpaintMaskColors = inpaintMaskColors.filter((entry) => entry.color !== normalized);
|
|
1474
|
-
inpaintAttachments = inpaintAttachments.filter((attachment) => attachment.scope !== "color" || attachment.color !== normalized);
|
|
1475
|
-
inpaintStrokes = inpaintStrokes.filter((stroke) => normalizeHexColor(stroke.color) !== normalized);
|
|
1476
|
-
redoStrokes = redoStrokes.filter((stroke) => normalizeHexColor(stroke.color) !== normalized);
|
|
1477
|
-
if (activeInpaintColor === normalized) {
|
|
1478
|
-
activeInpaintColor = inpaintMaskColors[0]?.color || "#ff2d55";
|
|
1479
|
-
inpaintColorPicker.value = activeInpaintColor;
|
|
1480
|
-
}
|
|
1481
|
-
redrawInpaintMask();
|
|
1482
|
-
renderInpaintControls();
|
|
1483
|
-
setStatus("", "");
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function selectInpaintColor(color) {
|
|
1487
|
-
activeInpaintColor = normalizeHexColor(color);
|
|
1488
|
-
inpaintColorPicker.value = activeInpaintColor;
|
|
1489
|
-
for (const row of Array.from(inpaintPrompts.querySelectorAll(".editor-inpaint-color-row"))) {
|
|
1490
|
-
if (row instanceof HTMLElement) {
|
|
1491
|
-
row.dataset.active = row.dataset.color === activeInpaintColor ? "true" : "false";
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
function updateInpaintPrompt(color, prompt) {
|
|
1497
|
-
const normalized = normalizeHexColor(color);
|
|
1498
|
-
inpaintMaskColors = inpaintMaskColors.map((entry) => (
|
|
1499
|
-
entry.color === normalized ? { ...entry, prompt: String(prompt || "") } : entry
|
|
1500
|
-
));
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
function buildInpaintInstruction() {
|
|
1504
|
-
const globalPrompt = inpaintGlobalPrompt.value.trim();
|
|
1505
|
-
const regions = inpaintMaskColors
|
|
1506
|
-
.map((entry) => ({ color: entry.color.toUpperCase(), prompt: String(entry.prompt || "").trim() }))
|
|
1507
|
-
.filter((entry) => entry.prompt);
|
|
1508
|
-
if (!globalPrompt && !regions.length) return "";
|
|
1509
|
-
const attachmentNotes = inpaintAttachments.map((attachment, index) => {
|
|
1510
|
-
const label = "reference image " + (index + 1) + " (" + (attachment.fileName || "attachment") + ")";
|
|
1511
|
-
return attachment.scope === "global"
|
|
1512
|
-
? label + " applies to the whole image instruction"
|
|
1513
|
-
: label + " applies only to the UI mask color " + String(attachment.color || "").toUpperCase();
|
|
1514
|
-
});
|
|
1515
|
-
return [
|
|
1516
|
-
"INPAINTING SYSTEM WRAPPER:",
|
|
1517
|
-
"You are editing an existing image with an alpha mask. Treat transparent mask pixels as the only editable region. Everything outside the editable mask must remain the same image: preserve identity, layout, pose, camera angle, framing, background, lighting, colors, textures, text, logos, and all unmasked details.",
|
|
1518
|
-
"Do not redraw, reinterpret, beautify, crop, zoom, rotate, expand, stylize, or otherwise alter unmasked content. Do not render the UI mask colors; they are labels only.",
|
|
1519
|
-
"The final image must keep the original dimensions and aspect ratio unless the user explicitly asks for a dimension change.",
|
|
1520
|
-
globalPrompt ? "Whole image prompt: " + globalPrompt + ". Apply this only as guidance for the masked edit and overall consistency; it does not permit changing unmasked content." : "",
|
|
1521
|
-
regions.map((entry) => (
|
|
1522
|
-
"Masked region " + entry.color + ": replace only that marked area with " + entry.prompt + ". Match the surrounding perspective, lighting, grain, shadows, and edge blending."
|
|
1523
|
-
)).join(" "),
|
|
1524
|
-
attachmentNotes.length ? "Reference image scope: " + attachmentNotes.join("; ") + ". Use each reference only for its stated scope and do not copy unrelated reference details into unmasked areas." : ""
|
|
1525
|
-
].filter(Boolean).join(" ");
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
function getInpaintAspectRatio() {
|
|
1529
|
-
const width = inpaintCanvas.width || inpaintImage.naturalWidth || 0;
|
|
1530
|
-
const height = inpaintCanvas.height || inpaintImage.naturalHeight || 0;
|
|
1531
|
-
if (!width || !height) return undefined;
|
|
1532
|
-
const ratio = width / height;
|
|
1533
|
-
if (Math.abs(ratio - 1) < 0.08) return "1:1";
|
|
1534
|
-
if (ratio > 1) return ratio >= 1.55 ? "16:9" : "4:3";
|
|
1535
|
-
return ratio <= 0.65 ? "9:16" : "3:4";
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
function canvasToPngBlob(canvas) {
|
|
1539
|
-
return new Promise((resolve, reject) => {
|
|
1540
|
-
canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("Unable to prepare image.")), "image/png");
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
async function createProviderMaskBlob() {
|
|
1545
|
-
const maskCanvas = document.createElement("canvas");
|
|
1546
|
-
maskCanvas.width = inpaintCanvas.width;
|
|
1547
|
-
maskCanvas.height = inpaintCanvas.height;
|
|
1548
|
-
const ctx = maskCanvas.getContext("2d");
|
|
1549
|
-
if (!ctx) throw new Error("Unable to prepare mask image.");
|
|
1550
|
-
ctx.fillStyle = "#ffffff";
|
|
1551
|
-
ctx.fillRect(0, 0, maskCanvas.width, maskCanvas.height);
|
|
1552
|
-
ctx.globalCompositeOperation = "destination-out";
|
|
1553
|
-
ctx.lineCap = "round";
|
|
1554
|
-
ctx.lineJoin = "round";
|
|
1555
|
-
for (const stroke of inpaintStrokes) {
|
|
1556
|
-
if (!stroke.points || stroke.points.length < 1) continue;
|
|
1557
|
-
ctx.strokeStyle = "rgba(0, 0, 0, 1)";
|
|
1558
|
-
ctx.lineWidth = stroke.size;
|
|
1559
|
-
ctx.beginPath();
|
|
1560
|
-
ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
|
|
1561
|
-
if (stroke.points.length === 1) {
|
|
1562
|
-
ctx.lineTo(stroke.points[0].x + 0.01, stroke.points[0].y + 0.01);
|
|
1563
|
-
} else {
|
|
1564
|
-
for (let index = 1; index < stroke.points.length; index += 1) {
|
|
1565
|
-
ctx.lineTo(stroke.points[index].x, stroke.points[index].y);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
ctx.stroke();
|
|
1569
|
-
}
|
|
1570
|
-
return canvasToPngBlob(maskCanvas);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
function parseJsonResponse(response) {
|
|
1574
|
-
return response.json().catch(() => ({}));
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
async function loadInpaintProviders() {
|
|
1578
|
-
try {
|
|
1579
|
-
const response = await fetch("/api/v1/user/me/provider-keys", { credentials: "same-origin" });
|
|
1580
|
-
const payload = await parseJsonResponse(response);
|
|
1581
|
-
if (!response.ok) return;
|
|
1582
|
-
const supported = new Set(["openai", "gemini", "openrouter"]);
|
|
1583
|
-
const providers = [];
|
|
1584
|
-
const seen = new Set();
|
|
1585
|
-
for (const key of Array.isArray(payload.provider_keys) ? payload.provider_keys : []) {
|
|
1586
|
-
const provider = String(key.provider || "");
|
|
1587
|
-
const status = String(key.status || "active");
|
|
1588
|
-
const cooldownUntil = key.cooldown_until ? Date.parse(String(key.cooldown_until)) : 0;
|
|
1589
|
-
if (!supported.has(provider) || status !== "active" || (cooldownUntil && cooldownUntil > Date.now()) || seen.has(provider)) {
|
|
1590
|
-
continue;
|
|
1591
|
-
}
|
|
1592
|
-
seen.add(provider);
|
|
1593
|
-
providers.push(provider);
|
|
1594
|
-
}
|
|
1595
|
-
inpaintProviderSelect.innerHTML = '<option value="">Auto</option>' + providers.map((provider) => (
|
|
1596
|
-
'<option value="' + provider + '">' + provider.charAt(0).toUpperCase() + provider.slice(1) + '</option>'
|
|
1597
|
-
)).join("");
|
|
1598
|
-
inpaintProviderWrap.setAttribute("data-visible", providers.length > 1 ? "true" : "false");
|
|
1599
|
-
} catch {}
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
async function uploadTemporaryBlob(blob, fileName, contentType) {
|
|
1603
|
-
const presignResponse = await fetch("/api/v1/user/me/temporary-files/presign", {
|
|
1604
|
-
method: "POST",
|
|
1605
|
-
credentials: "same-origin",
|
|
1606
|
-
headers: { "content-type": "application/json" },
|
|
1607
|
-
body: JSON.stringify({
|
|
1608
|
-
file_name: fileName,
|
|
1609
|
-
content_type: contentType,
|
|
1610
|
-
size_bytes: blob.size,
|
|
1611
|
-
folder_path: "inpaint"
|
|
1612
|
-
})
|
|
1613
|
-
});
|
|
1614
|
-
const presignPayload = await parseJsonResponse(presignResponse);
|
|
1615
|
-
if (!presignResponse.ok || !presignPayload || !presignPayload.transport) {
|
|
1616
|
-
throw new Error(presignPayload && presignPayload.error ? presignPayload.error : "Unable to prepare upload.");
|
|
1617
|
-
}
|
|
1618
|
-
if (presignPayload.transport === "server") {
|
|
1619
|
-
const formData = new FormData();
|
|
1620
|
-
formData.append(presignPayload.upload?.form_field || "file", new File([blob], fileName, { type: contentType }));
|
|
1621
|
-
formData.append("folder_path", "inpaint");
|
|
1622
|
-
const uploadResponse = await fetch(presignPayload.upload?.url || "/api/v1/user/me/temporary-files/upload", {
|
|
1623
|
-
method: presignPayload.upload?.method || "POST",
|
|
1624
|
-
credentials: "same-origin",
|
|
1625
|
-
body: formData
|
|
1626
|
-
});
|
|
1627
|
-
const uploadPayload = await parseJsonResponse(uploadResponse);
|
|
1628
|
-
if (!uploadResponse.ok || !uploadPayload.file) {
|
|
1629
|
-
throw new Error(uploadPayload.error || "Unable to upload file.");
|
|
1630
|
-
}
|
|
1631
|
-
return uploadPayload.file.viewUrl || uploadPayload.file.view_url || uploadPayload.file.s3Url || uploadPayload.file.s3_url;
|
|
1632
|
-
}
|
|
1633
|
-
const uploadResponse = await fetch(presignPayload.upload.url, {
|
|
1634
|
-
method: presignPayload.upload.method,
|
|
1635
|
-
headers: presignPayload.upload.headers,
|
|
1636
|
-
body: blob
|
|
1637
|
-
});
|
|
1638
|
-
if (!uploadResponse.ok) throw new Error("Upload failed with status " + uploadResponse.status + ".");
|
|
1639
|
-
const finalizeResponse = await fetch("/api/v1/user/me/temporary-files", {
|
|
1640
|
-
method: "POST",
|
|
1641
|
-
credentials: "same-origin",
|
|
1642
|
-
headers: { "content-type": "application/json" },
|
|
1643
|
-
body: JSON.stringify({
|
|
1644
|
-
file_id: presignPayload.file_id,
|
|
1645
|
-
file_name: presignPayload.file_name,
|
|
1646
|
-
content_type: presignPayload.content_type,
|
|
1647
|
-
size_bytes: blob.size,
|
|
1648
|
-
storage_key: presignPayload.storage_key,
|
|
1649
|
-
folder_path: presignPayload.folder_path || "inpaint"
|
|
1650
|
-
})
|
|
1651
|
-
});
|
|
1652
|
-
const finalizePayload = await parseJsonResponse(finalizeResponse);
|
|
1653
|
-
if (!finalizeResponse.ok || !finalizePayload.file) {
|
|
1654
|
-
throw new Error(finalizePayload.error || "Unable to save file.");
|
|
1655
|
-
}
|
|
1656
|
-
return finalizePayload.file.viewUrl || finalizePayload.file.view_url || finalizePayload.file.s3Url || finalizePayload.file.s3_url;
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
function absoluteUrl(value) {
|
|
1660
|
-
return new URL(value, window.location.href).toString();
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
function firstSearchParam(names) {
|
|
1664
|
-
const params = new URLSearchParams(window.location.search);
|
|
1665
|
-
for (const name of names) {
|
|
1666
|
-
const value = params.get(name);
|
|
1667
|
-
if (value && value.trim()) return value.trim();
|
|
1668
|
-
}
|
|
1669
|
-
return "";
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
function sourceNameFromUrl(value) {
|
|
1673
|
-
try {
|
|
1674
|
-
const pathName = new URL(value, window.location.href).pathname;
|
|
1675
|
-
const fileName = decodeURIComponent(pathName.split("/").filter(Boolean).pop() || "");
|
|
1676
|
-
return fileName || "Source image";
|
|
1677
|
-
} catch {
|
|
1678
|
-
return "Source image";
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
function loadSourceUrl(value) {
|
|
1683
|
-
if (!value) return;
|
|
1684
|
-
let url = "";
|
|
1685
|
-
try {
|
|
1686
|
-
url = absoluteUrl(value);
|
|
1687
|
-
} catch {
|
|
1688
|
-
setStatus("The source image URL is invalid.", "error");
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
sourceImageUrl = url;
|
|
1692
|
-
sourceImageName = firstSearchParam(["source_image_name", "image_name", "file_name"]) || sourceNameFromUrl(url);
|
|
1693
|
-
sourceName.textContent = sourceImageName;
|
|
1694
|
-
emptyState.hidden = true;
|
|
1695
|
-
canvasWrap.hidden = false;
|
|
1696
|
-
outputPanel.hidden = true;
|
|
1697
|
-
resultImage.removeAttribute("src");
|
|
1698
|
-
resultDownload.href = "#";
|
|
1699
|
-
resultDownload.removeAttribute("download");
|
|
1700
|
-
resetMask();
|
|
1701
|
-
inpaintImage.onload = () => {
|
|
1702
|
-
syncInpaintCanvasSize();
|
|
1703
|
-
resetMask();
|
|
1704
|
-
};
|
|
1705
|
-
inpaintImage.src = sourceImageUrl;
|
|
1706
|
-
setStatus("Source ready from URL.", "success");
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
function clearInpaintUrlParams() {
|
|
1710
|
-
const url = new URL(window.location.href);
|
|
1711
|
-
for (const key of ["tracer", "tracer_id", "trace", "source_image_url", "image_url", "input_image_url", "image", "source_image_name", "image_name", "file_name"]) {
|
|
1712
|
-
url.searchParams.delete(key);
|
|
1713
|
-
}
|
|
1714
|
-
window.history.replaceState({}, "", url.toString());
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
function initializeFromUrl() {
|
|
1718
|
-
tracerInput.value = firstSearchParam(["tracer", "tracer_id", "trace"]) || makeTracerId();
|
|
1719
|
-
loadSourceUrl(firstSearchParam(["source_image_url", "image_url", "input_image_url", "image"]));
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
async function addAttachmentFilesForScope(files, scope) {
|
|
1723
|
-
if (!scope) return;
|
|
1724
|
-
const selected = Array.from(files || []);
|
|
1725
|
-
if (!selected.length) return;
|
|
1726
|
-
if (inpaintAttachments.length + selected.length > maxInpaintAttachments) {
|
|
1727
|
-
setStatus("Use up to " + maxInpaintAttachments + " reference images.", "error");
|
|
1728
|
-
return;
|
|
1729
|
-
}
|
|
1730
|
-
const previousDisabled = inpaintSubmit.disabled;
|
|
1731
|
-
inpaintSubmit.disabled = true;
|
|
1732
|
-
setStatus("Uploading reference image...", "");
|
|
1733
|
-
try {
|
|
1734
|
-
for (const file of selected) {
|
|
1735
|
-
if (!isAllowedInpaintAttachment(file)) {
|
|
1736
|
-
throw new Error("Reference attachments must be JPEG, PNG, or WebP images.");
|
|
1737
|
-
}
|
|
1738
|
-
const fileName = file.name || "reference.png";
|
|
1739
|
-
const uploadedUrl = await uploadTemporaryBlob(file, fileName, file.type || "image/png");
|
|
1740
|
-
inpaintAttachments.push({
|
|
1741
|
-
id: attachmentId(),
|
|
1742
|
-
scope: scope.scope === "color" ? "color" : "global",
|
|
1743
|
-
color: scope.scope === "color" ? normalizeHexColor(scope.color) : "",
|
|
1744
|
-
fileName,
|
|
1745
|
-
url: absoluteUrl(uploadedUrl)
|
|
1746
|
-
});
|
|
1747
|
-
}
|
|
1748
|
-
renderAllAttachmentChips();
|
|
1749
|
-
setStatus("Reference image ready.", "success");
|
|
1750
|
-
} catch (error) {
|
|
1751
|
-
console.error(error);
|
|
1752
|
-
setStatus(error instanceof Error ? error.message : "Unable to upload reference image.", "error");
|
|
1753
|
-
} finally {
|
|
1754
|
-
inpaintSubmit.disabled = previousDisabled;
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
async function addAttachmentFiles(files) {
|
|
1759
|
-
const scope = pendingInpaintAttachmentScope;
|
|
1760
|
-
pendingInpaintAttachmentScope = null;
|
|
1761
|
-
await addAttachmentFilesForScope(files, scope);
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
function resetMask() {
|
|
1765
|
-
inpaintStrokes = [];
|
|
1766
|
-
redoStrokes = [];
|
|
1767
|
-
currentInpaintStroke = null;
|
|
1768
|
-
isInpaintPainting = false;
|
|
1769
|
-
redrawInpaintMask();
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
function resetWorkspace() {
|
|
1773
|
-
sourceImageUrl = "";
|
|
1774
|
-
sourceImageName = "";
|
|
1775
|
-
sourceName.textContent = "No image selected";
|
|
1776
|
-
emptyState.hidden = false;
|
|
1777
|
-
canvasWrap.hidden = true;
|
|
1778
|
-
outputPanel.hidden = true;
|
|
1779
|
-
resultImage.removeAttribute("src");
|
|
1780
|
-
resultDownload.href = "#";
|
|
1781
|
-
resultDownload.removeAttribute("download");
|
|
1782
|
-
inpaintImage.removeAttribute("src");
|
|
1783
|
-
inpaintCanvas.width = 1;
|
|
1784
|
-
inpaintCanvas.height = 1;
|
|
1785
|
-
inpaintGlobalPrompt.value = "";
|
|
1786
|
-
inpaintMaskColors = [{ color: "#ff2d55", prompt: "" }];
|
|
1787
|
-
inpaintAttachments = [];
|
|
1788
|
-
pendingInpaintAttachmentScope = null;
|
|
1789
|
-
activeInpaintColor = "#ff2d55";
|
|
1790
|
-
inpaintColorPicker.value = activeInpaintColor;
|
|
1791
|
-
resetMask();
|
|
1792
|
-
renderInpaintControls();
|
|
1793
|
-
setStatus("", "");
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
async function loadSourceFile(file) {
|
|
1797
|
-
if (!file || !file.type.startsWith("image/")) {
|
|
1798
|
-
setStatus("Choose an image file.", "error");
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
uploadButton.disabled = true;
|
|
1802
|
-
inpaintSubmit.disabled = true;
|
|
1803
|
-
setStatus("Uploading source image...", "");
|
|
1804
|
-
try {
|
|
1805
|
-
const fileName = file.name || "source.png";
|
|
1806
|
-
const uploadedUrl = await uploadTemporaryBlob(file, fileName, file.type || "image/png");
|
|
1807
|
-
sourceImageUrl = absoluteUrl(uploadedUrl);
|
|
1808
|
-
sourceImageName = fileName;
|
|
1809
|
-
sourceName.textContent = fileName;
|
|
1810
|
-
emptyState.hidden = true;
|
|
1811
|
-
canvasWrap.hidden = false;
|
|
1812
|
-
outputPanel.hidden = true;
|
|
1813
|
-
resultImage.removeAttribute("src");
|
|
1814
|
-
resultDownload.href = "#";
|
|
1815
|
-
resultDownload.removeAttribute("download");
|
|
1816
|
-
resetMask();
|
|
1817
|
-
inpaintImage.onload = () => {
|
|
1818
|
-
syncInpaintCanvasSize();
|
|
1819
|
-
resetMask();
|
|
1820
|
-
};
|
|
1821
|
-
inpaintImage.src = sourceImageUrl;
|
|
1822
|
-
setStatus("Source ready.", "success");
|
|
1823
|
-
} catch (error) {
|
|
1824
|
-
console.error(error);
|
|
1825
|
-
setStatus(error instanceof Error ? error.message : "Unable to upload source image.", "error");
|
|
1826
|
-
} finally {
|
|
1827
|
-
uploadButton.disabled = false;
|
|
1828
|
-
inpaintSubmit.disabled = false;
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
function extractOutputUrl(payload) {
|
|
1833
|
-
const result = payload && payload.result ? payload.result : {};
|
|
1834
|
-
return result.output?.image?.file_url
|
|
1835
|
-
|| result.output?.primary_file_url
|
|
1836
|
-
|| result.image?.file_url
|
|
1837
|
-
|| result.primary_file_url
|
|
1838
|
-
|| (Array.isArray(result.output?.files) ? result.output.files[0] : "")
|
|
1839
|
-
|| (Array.isArray(result.files) ? result.files[0] : "");
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
function isPendingJobStatus(status) {
|
|
1843
|
-
return ["queued", "running", "waiting_for_provider", "waiting_for_child", "waiting_for_human"].includes(String(status || ""));
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
function pendingHistoryItemKey(jobId) {
|
|
1847
|
-
return "job:" + String(jobId || "");
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
function makePendingInpaintHistoryItem(input) {
|
|
1851
|
-
const jobId = String(input?.jobId || input?.job_id || "");
|
|
1852
|
-
if (!jobId) return null;
|
|
1853
|
-
return {
|
|
1854
|
-
key: pendingHistoryItemKey(jobId),
|
|
1855
|
-
kind: "pending",
|
|
1856
|
-
jobId,
|
|
1857
|
-
status: String(input?.status || "queued"),
|
|
1858
|
-
progress: Number(input?.progress || 0),
|
|
1859
|
-
createdAt: String(input?.createdAt || input?.created_at || new Date().toISOString()),
|
|
1860
|
-
updatedAt: String(input?.updatedAt || input?.updated_at || new Date().toISOString())
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
function upsertInpaintHistoryItem(item) {
|
|
1865
|
-
if (!item || !item.key) return;
|
|
1866
|
-
inpaintHistoryItems = inpaintHistoryItems.filter((entry) => (
|
|
1867
|
-
entry.key !== item.key
|
|
1868
|
-
&& (!item.jobId || entry.jobId !== item.jobId)
|
|
1869
|
-
));
|
|
1870
|
-
inpaintHistoryItems.unshift(item);
|
|
1871
|
-
inpaintHistoryItems.sort((a, b) => Date.parse(b.createdAt || 0) - Date.parse(a.createdAt || 0));
|
|
1872
|
-
renderInpaintHistory();
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
function applyCompletedInpaintJob(payload) {
|
|
1876
|
-
const outputUrl = extractOutputUrl(payload);
|
|
1877
|
-
if (!outputUrl) return "";
|
|
1878
|
-
const url = absoluteUrl(outputUrl);
|
|
1879
|
-
const jobId = String(payload?.job_id || payload?.jobId || "");
|
|
1880
|
-
resultImage.src = url;
|
|
1881
|
-
resultDownload.href = url;
|
|
1882
|
-
resultDownload.setAttribute("download", "inpaint-" + Date.now() + ".png");
|
|
1883
|
-
outputPanel.hidden = false;
|
|
1884
|
-
upsertInpaintHistoryItem({
|
|
1885
|
-
key: historyItemKey({ jobId: jobId || "inpaint" }, url, 0),
|
|
1886
|
-
kind: "output",
|
|
1887
|
-
jobId,
|
|
1888
|
-
url,
|
|
1889
|
-
createdAt: String(payload?.completed_at || payload?.updated_at || new Date().toISOString())
|
|
1890
|
-
});
|
|
1891
|
-
return url;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
function historyEntryUrls(entry) {
|
|
1895
|
-
const urls = Array.isArray(entry?.outputMediaUrls) ? entry.outputMediaUrls : [];
|
|
1896
|
-
return urls.filter((url) => {
|
|
1897
|
-
const value = String(url || "");
|
|
1898
|
-
if (!/\\.(png|jpe?g|webp|gif|avif)(\\?|#|$)/i.test(value)) {
|
|
1899
|
-
return false;
|
|
1900
|
-
}
|
|
1901
|
-
try {
|
|
1902
|
-
const parsed = new URL(value, window.location.href);
|
|
1903
|
-
const storageKey = parsed.searchParams.get("key") || decodeURIComponent(parsed.pathname).replace(/^\\/+/, "");
|
|
1904
|
-
return !/\\/temporary\\/inpaint(?:-|\\/|$)/i.test(storageKey)
|
|
1905
|
-
&& !/\\/temporary\\/inpaint-masks(?:\\/|$)/i.test(storageKey)
|
|
1906
|
-
&& !/inpaint-mask-[^/]+\\.png/i.test(storageKey);
|
|
1907
|
-
} catch {
|
|
1908
|
-
return !/\\/temporary\\/inpaint(?:-|\\/|$)/i.test(value)
|
|
1909
|
-
&& !/\\/temporary\\/inpaint-masks(?:\\/|$)/i.test(value)
|
|
1910
|
-
&& !/inpaint-mask-[^/]+\\.png/i.test(value);
|
|
1911
|
-
}
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
function isInpaintHistoryEntry(entry) {
|
|
1916
|
-
const templateId = String(entry?.linkedJob?.template_id || entry?.linkedJob?.templateId || "").toLowerCase();
|
|
1917
|
-
const path = String(entry?.path || "").toLowerCase();
|
|
1918
|
-
return templateId === "primitive:image_inpaint"
|
|
1919
|
-
|| path.includes("/primitives/images/inpaint")
|
|
1920
|
-
|| path.includes("/primitives/image_inpaint");
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
function historyItemKey(entry, url, index) {
|
|
1924
|
-
return String(entry?.jobId || entry?.job_id || entry?.id || "inpaint") + ":" + index + ":" + String(url || "");
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
function setHistoryEmpty(message) {
|
|
1928
|
-
historyList.innerHTML = "";
|
|
1929
|
-
const empty = document.createElement("div");
|
|
1930
|
-
empty.className = "custom-inpaint-history-empty";
|
|
1931
|
-
empty.textContent = message;
|
|
1932
|
-
historyList.append(empty);
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
function renderInpaintHistory() {
|
|
1936
|
-
historyList.innerHTML = "";
|
|
1937
|
-
const visible = inpaintHistoryItems.filter((item) => !hiddenInpaintHistoryKeys.has(item.key));
|
|
1938
|
-
if (!visible.length) {
|
|
1939
|
-
setHistoryEmpty("No inpaint outputs yet.");
|
|
1940
|
-
return;
|
|
1941
|
-
}
|
|
1942
|
-
for (const item of visible) {
|
|
1943
|
-
const row = document.createElement("div");
|
|
1944
|
-
row.className = "custom-inpaint-history-item";
|
|
1945
|
-
row.dataset.historyKey = item.key;
|
|
1946
|
-
let thumbButton;
|
|
1947
|
-
if (item.kind === "pending") {
|
|
1948
|
-
thumbButton = document.createElement("div");
|
|
1949
|
-
thumbButton.className = "custom-inpaint-history-generating";
|
|
1950
|
-
thumbButton.setAttribute("aria-label", "Image edit generating");
|
|
1951
|
-
thumbButton.setAttribute("role", "img");
|
|
1952
|
-
} else {
|
|
1953
|
-
thumbButton = document.createElement("button");
|
|
1954
|
-
thumbButton.type = "button";
|
|
1955
|
-
thumbButton.className = "custom-inpaint-history-thumb-button";
|
|
1956
|
-
thumbButton.dataset.historyPreview = item.key;
|
|
1957
|
-
thumbButton.setAttribute("aria-label", "View inpaint output");
|
|
1958
|
-
const thumb = document.createElement("img");
|
|
1959
|
-
thumb.className = "custom-inpaint-history-thumb";
|
|
1960
|
-
thumb.src = item.url;
|
|
1961
|
-
thumb.alt = "Image edit output";
|
|
1962
|
-
thumb.loading = "lazy";
|
|
1963
|
-
thumbButton.append(thumb);
|
|
1964
|
-
}
|
|
1965
|
-
const meta = document.createElement("div");
|
|
1966
|
-
meta.className = "custom-inpaint-history-meta";
|
|
1967
|
-
const title = document.createElement("div");
|
|
1968
|
-
title.className = "custom-inpaint-history-title";
|
|
1969
|
-
title.textContent = item.kind === "pending"
|
|
1970
|
-
? "Generating inpaint"
|
|
1971
|
-
: (item.createdAt ? new Date(item.createdAt).toLocaleString() : "Image edit output");
|
|
1972
|
-
if (item.kind === "pending") {
|
|
1973
|
-
const subtitle = document.createElement("div");
|
|
1974
|
-
subtitle.className = "custom-inpaint-history-subtitle";
|
|
1975
|
-
const progress = Number(item.progress || 0);
|
|
1976
|
-
subtitle.textContent = "Job " + (item.status || "queued") + (progress > 0 ? " · " + Math.round(progress * 100) + "%" : "");
|
|
1977
|
-
meta.append(title, subtitle);
|
|
1978
|
-
} else {
|
|
1979
|
-
meta.append(title);
|
|
1980
|
-
}
|
|
1981
|
-
const actions = document.createElement("div");
|
|
1982
|
-
actions.className = "custom-inpaint-history-actions";
|
|
1983
|
-
if (item.kind === "pending") {
|
|
1984
|
-
const check = document.createElement("button");
|
|
1985
|
-
check.type = "button";
|
|
1986
|
-
check.className = "custom-inpaint-history-check";
|
|
1987
|
-
check.dataset.historyCheck = item.jobId || "";
|
|
1988
|
-
check.textContent = "Check Status";
|
|
1989
|
-
actions.append(check);
|
|
1990
|
-
} else {
|
|
1991
|
-
const download = document.createElement("a");
|
|
1992
|
-
download.className = "custom-inpaint-history-icon";
|
|
1993
|
-
download.href = item.url;
|
|
1994
|
-
download.target = "_blank";
|
|
1995
|
-
download.rel = "noopener noreferrer";
|
|
1996
|
-
download.download = imageDownloadName("inpaint-history", item.createdAt);
|
|
1997
|
-
download.setAttribute("aria-label", "Download inpaint output");
|
|
1998
|
-
download.setAttribute("title", "Download");
|
|
1999
|
-
download.innerHTML = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3v9"></path><path d="m6.5 8.5 3.5 3.5 3.5-3.5"></path><path d="M4 16h12"></path></svg>';
|
|
2000
|
-
const copy = document.createElement("button");
|
|
2001
|
-
copy.type = "button";
|
|
2002
|
-
copy.className = "custom-inpaint-history-icon";
|
|
2003
|
-
copy.dataset.historyCopy = item.key;
|
|
2004
|
-
copy.setAttribute("aria-label", "Copy inpaint output URL");
|
|
2005
|
-
copy.setAttribute("title", "Copy URL");
|
|
2006
|
-
copy.innerHTML = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.4 11.6a3.2 3.2 0 0 0 4.5 0l2.2-2.2a3.2 3.2 0 0 0-4.5-4.5L9.4 6.1"></path><path d="M11.6 8.4a3.2 3.2 0 0 0-4.5 0L4.9 10.6a3.2 3.2 0 0 0 4.5 4.5l1.2-1.2"></path></svg>';
|
|
2007
|
-
actions.append(download, copy);
|
|
2008
|
-
}
|
|
2009
|
-
const remove = document.createElement("button");
|
|
2010
|
-
remove.type = "button";
|
|
2011
|
-
remove.className = "custom-inpaint-history-icon";
|
|
2012
|
-
remove.dataset.historyRemove = item.key;
|
|
2013
|
-
remove.setAttribute("aria-label", "Remove inpaint output from history strip");
|
|
2014
|
-
remove.setAttribute("title", "Remove");
|
|
2015
|
-
remove.innerHTML = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 5l10 10"></path><path d="M15 5 5 15"></path></svg>';
|
|
2016
|
-
actions.append(remove);
|
|
2017
|
-
meta.append(actions);
|
|
2018
|
-
const confirm = document.createElement("div");
|
|
2019
|
-
confirm.className = "custom-inpaint-history-popconfirm";
|
|
2020
|
-
confirm.hidden = true;
|
|
2021
|
-
confirm.dataset.historyConfirm = item.key;
|
|
2022
|
-
const confirmText = document.createElement("p");
|
|
2023
|
-
confirmText.textContent = "Remove from this history strip?";
|
|
2024
|
-
const confirmActions = document.createElement("div");
|
|
2025
|
-
confirmActions.className = "custom-inpaint-history-popconfirm-actions";
|
|
2026
|
-
const cancel = document.createElement("button");
|
|
2027
|
-
cancel.type = "button";
|
|
2028
|
-
cancel.dataset.historyCancel = item.key;
|
|
2029
|
-
cancel.textContent = "Cancel";
|
|
2030
|
-
const confirmRemove = document.createElement("button");
|
|
2031
|
-
confirmRemove.type = "button";
|
|
2032
|
-
confirmRemove.dataset.historyConfirmRemove = item.key;
|
|
2033
|
-
confirmRemove.dataset.danger = "true";
|
|
2034
|
-
confirmRemove.textContent = "Remove";
|
|
2035
|
-
confirmActions.append(cancel, confirmRemove);
|
|
2036
|
-
confirm.append(confirmText, confirmActions);
|
|
2037
|
-
row.append(thumbButton, meta, confirm);
|
|
2038
|
-
historyList.append(row);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
function closeInpaintHistoryConfirms(exceptKey) {
|
|
2043
|
-
historyList.querySelectorAll("[data-history-confirm]").forEach((entry) => {
|
|
2044
|
-
if (!(entry instanceof HTMLElement)) return;
|
|
2045
|
-
entry.hidden = exceptKey ? entry.dataset.historyConfirm !== exceptKey : true;
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
function extractInpaintHistoryItems(entries) {
|
|
2050
|
-
const items = [];
|
|
2051
|
-
const seen = new Set();
|
|
2052
|
-
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
2053
|
-
if (!isInpaintHistoryEntry(entry)) continue;
|
|
2054
|
-
const linkedJob = entry?.linkedJob || null;
|
|
2055
|
-
if (linkedJob && isPendingJobStatus(linkedJob.status)) {
|
|
2056
|
-
const pending = makePendingInpaintHistoryItem({
|
|
2057
|
-
job_id: linkedJob.job_id,
|
|
2058
|
-
status: linkedJob.status,
|
|
2059
|
-
progress: linkedJob.progress,
|
|
2060
|
-
created_at: linkedJob.created_at || entry.createdAt,
|
|
2061
|
-
updated_at: linkedJob.updated_at || entry.createdAt
|
|
2062
|
-
});
|
|
2063
|
-
if (pending && !seen.has(pending.key)) {
|
|
2064
|
-
seen.add(pending.key);
|
|
2065
|
-
items.push(pending);
|
|
2066
|
-
}
|
|
2067
|
-
continue;
|
|
2068
|
-
}
|
|
2069
|
-
historyEntryUrls(entry).forEach((rawUrl, index) => {
|
|
2070
|
-
const url = absoluteUrl(rawUrl);
|
|
2071
|
-
const key = historyItemKey(entry, url, index);
|
|
2072
|
-
if (seen.has(key)) return;
|
|
2073
|
-
seen.add(key);
|
|
2074
|
-
items.push({
|
|
2075
|
-
key,
|
|
2076
|
-
kind: "output",
|
|
2077
|
-
jobId: String(entry?.jobId || entry?.job_id || entry?.linkedJob?.job_id || ""),
|
|
2078
|
-
url,
|
|
2079
|
-
createdAt: String(entry.createdAt || new Date().toISOString())
|
|
2080
|
-
});
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
return items;
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
async function copyText(value) {
|
|
2087
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
2088
|
-
try {
|
|
2089
|
-
await navigator.clipboard.writeText(value);
|
|
2090
|
-
return true;
|
|
2091
|
-
} catch {}
|
|
2092
|
-
}
|
|
2093
|
-
const field = document.createElement("textarea");
|
|
2094
|
-
field.value = value;
|
|
2095
|
-
field.setAttribute("readonly", "");
|
|
2096
|
-
field.style.position = "fixed";
|
|
2097
|
-
field.style.opacity = "0";
|
|
2098
|
-
document.body.appendChild(field);
|
|
2099
|
-
field.select();
|
|
2100
|
-
let copied = false;
|
|
2101
|
-
try {
|
|
2102
|
-
copied = document.execCommand("copy");
|
|
2103
|
-
} finally {
|
|
2104
|
-
field.remove();
|
|
2105
|
-
}
|
|
2106
|
-
return copied;
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
async function loadInpaintHistory() {
|
|
2110
|
-
const tracer = getTracerValue();
|
|
2111
|
-
historyRefresh.disabled = true;
|
|
2112
|
-
try {
|
|
2113
|
-
const endpoint = new URL("/job-runs/history", window.location.origin);
|
|
2114
|
-
endpoint.searchParams.set("limit", "120");
|
|
2115
|
-
endpoint.searchParams.set("template_id", "primitive:image_inpaint");
|
|
2116
|
-
endpoint.searchParams.set("tracer", tracer);
|
|
2117
|
-
const response = await fetch(endpoint, { credentials: "same-origin" });
|
|
2118
|
-
const payload = await parseJsonResponse(response);
|
|
2119
|
-
if (!response.ok || (payload && payload.error)) throw new Error(payload.error || "Unable to load inpaint history.");
|
|
2120
|
-
inpaintHistoryItems = extractInpaintHistoryItems(payload.entries);
|
|
2121
|
-
renderInpaintHistory();
|
|
2122
|
-
} catch (error) {
|
|
2123
|
-
console.error(error);
|
|
2124
|
-
setHistoryEmpty(error instanceof Error ? error.message : "Unable to load inpaint history.");
|
|
2125
|
-
} finally {
|
|
2126
|
-
historyRefresh.disabled = false;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
async function checkInpaintJobStatus(jobId, button) {
|
|
2131
|
-
if (!jobId) return null;
|
|
2132
|
-
if (button instanceof HTMLButtonElement) {
|
|
2133
|
-
button.disabled = true;
|
|
2134
|
-
button.textContent = "Checking";
|
|
2135
|
-
}
|
|
2136
|
-
try {
|
|
2137
|
-
const response = await fetch("/api/v1/primitives/image_inpaint/jobs/" + encodeURIComponent(jobId), {
|
|
2138
|
-
credentials: "same-origin"
|
|
2139
|
-
});
|
|
2140
|
-
const payload = await parseJsonResponse(response);
|
|
2141
|
-
if (!response.ok) throw new Error(payload.error || "Unable to check inpaint job.");
|
|
2142
|
-
if (payload.status === "failed" || payload.status === "cancelled") {
|
|
2143
|
-
const pending = makePendingInpaintHistoryItem(payload);
|
|
2144
|
-
upsertInpaintHistoryItem({
|
|
2145
|
-
...(pending || { key: pendingHistoryItemKey(jobId), kind: "pending", jobId, createdAt: new Date().toISOString() }),
|
|
2146
|
-
status: payload.status,
|
|
2147
|
-
progress: Number(payload.progress || 0)
|
|
2148
|
-
});
|
|
2149
|
-
throw new Error(payload.error?.message || "Image edit job " + payload.status + ".");
|
|
2150
|
-
}
|
|
2151
|
-
const outputUrl = extractOutputUrl(payload);
|
|
2152
|
-
if (payload.status === "succeeded" || payload.status === "complete" || outputUrl) {
|
|
2153
|
-
const url = applyCompletedInpaintJob(payload);
|
|
2154
|
-
if (!url) throw new Error("Image edit completed without an output URL.");
|
|
2155
|
-
setStatus("Image edit complete.", "success");
|
|
2156
|
-
return payload;
|
|
2157
|
-
}
|
|
2158
|
-
upsertInpaintHistoryItem(makePendingInpaintHistoryItem(payload));
|
|
2159
|
-
setStatus("Image edit job " + jobId + " is " + (payload.status || "running") + ".", "");
|
|
2160
|
-
return payload;
|
|
2161
|
-
} catch (error) {
|
|
2162
|
-
console.error(error);
|
|
2163
|
-
setStatus(error instanceof Error ? error.message : "Unable to check inpaint job.", "error");
|
|
2164
|
-
return null;
|
|
2165
|
-
} finally {
|
|
2166
|
-
if (button instanceof HTMLButtonElement) {
|
|
2167
|
-
button.disabled = false;
|
|
2168
|
-
button.textContent = "Check Status";
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
async function pollInpaintJob(jobId) {
|
|
2174
|
-
for (let attempt = 0; attempt < 90; attempt += 1) {
|
|
2175
|
-
const response = await fetch("/api/v1/primitives/image_inpaint/jobs/" + encodeURIComponent(jobId), {
|
|
2176
|
-
credentials: "same-origin"
|
|
2177
|
-
});
|
|
2178
|
-
const payload = await parseJsonResponse(response);
|
|
2179
|
-
if (!response.ok) throw new Error(payload.error || "Unable to poll inpaint job.");
|
|
2180
|
-
if (payload.status === "failed" || payload.status === "cancelled") {
|
|
2181
|
-
throw new Error(payload.error?.message || "Image edit job " + payload.status + ".");
|
|
2182
|
-
}
|
|
2183
|
-
const outputUrl = extractOutputUrl(payload);
|
|
2184
|
-
if (payload.status === "succeeded" || payload.status === "complete" || outputUrl) {
|
|
2185
|
-
if (!outputUrl) throw new Error("Image edit completed without an output URL.");
|
|
2186
|
-
applyCompletedInpaintJob(payload);
|
|
2187
|
-
setStatus("Image edit complete.", "success");
|
|
2188
|
-
return outputUrl;
|
|
2189
|
-
}
|
|
2190
|
-
upsertInpaintHistoryItem(makePendingInpaintHistoryItem(payload));
|
|
2191
|
-
setStatus("Image edit job " + jobId + " is " + (payload.status || "running") + ".", "");
|
|
2192
|
-
await new Promise((resolve) => window.setTimeout(resolve, attempt < 8 ? 1500 : 3000));
|
|
2193
|
-
}
|
|
2194
|
-
upsertInpaintHistoryItem(makePendingInpaintHistoryItem({ job_id: jobId, status: "running" }));
|
|
2195
|
-
setStatus("Image edit job is still running. Use Check Status in History.", "");
|
|
2196
|
-
return null;
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
async function submitInpaintJob() {
|
|
2200
|
-
if (!sourceImageUrl || inpaintSubmit.disabled) {
|
|
2201
|
-
setStatus("Upload a source image first.", "error");
|
|
2202
|
-
return;
|
|
2203
|
-
}
|
|
2204
|
-
if (!inpaintStrokes.length) {
|
|
2205
|
-
setStatus("Paint at least one mask region.", "error");
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
const usedColors = new Set(inpaintStrokes.map((stroke) => normalizeHexColor(stroke.color)));
|
|
2209
|
-
const globalPrompt = inpaintGlobalPrompt.value.trim();
|
|
2210
|
-
const missingPromptColor = inpaintMaskColors.find((entry) => usedColors.has(entry.color) && !String(entry.prompt || "").trim());
|
|
2211
|
-
if (!globalPrompt && missingPromptColor) {
|
|
2212
|
-
setStatus("Add a replacement prompt for " + missingPromptColor.color.toUpperCase() + ".", "error");
|
|
2213
|
-
return;
|
|
2214
|
-
}
|
|
2215
|
-
const instruction = buildInpaintInstruction();
|
|
2216
|
-
if (!instruction) {
|
|
2217
|
-
setStatus("Add a replacement prompt for at least one color.", "error");
|
|
2218
|
-
return;
|
|
2219
|
-
}
|
|
2220
|
-
setSubmitLoading(true);
|
|
2221
|
-
uploadButton.disabled = true;
|
|
2222
|
-
setStatus("Uploading masks...", "");
|
|
2223
|
-
try {
|
|
2224
|
-
const maskUrl = await uploadTemporaryBlob(await createProviderMaskBlob(), "inpaint-mask-" + Date.now() + ".png", "image/png");
|
|
2225
|
-
const tracer = getTracerValue();
|
|
2226
|
-
setStatus("Creating image edit job...", "");
|
|
2227
|
-
const response = await fetch("/api/v1/primitives/images/inpaint", {
|
|
2228
|
-
method: "POST",
|
|
2229
|
-
credentials: "same-origin",
|
|
2230
|
-
headers: { "content-type": "application/json" },
|
|
2231
|
-
body: JSON.stringify({
|
|
2232
|
-
tracer,
|
|
2233
|
-
payload: {
|
|
2234
|
-
source_image_url: sourceImageUrl,
|
|
2235
|
-
mask_url: absoluteUrl(maskUrl),
|
|
2236
|
-
instruction,
|
|
2237
|
-
reference_attachments: inpaintAttachments.map((attachment) => attachment.url),
|
|
2238
|
-
...(inpaintProviderSelect.value ? { provider: inpaintProviderSelect.value } : {}),
|
|
2239
|
-
size: "auto",
|
|
2240
|
-
...(getInpaintAspectRatio() ? { aspect_ratio: getInpaintAspectRatio() } : {}),
|
|
2241
|
-
output_format: "png"
|
|
2242
|
-
}
|
|
2243
|
-
})
|
|
2244
|
-
});
|
|
2245
|
-
const payload = await parseJsonResponse(response);
|
|
2246
|
-
if (!response.ok) throw new Error(payload.error || "Unable to create image edit job.");
|
|
2247
|
-
setStatus("Image edit job " + (payload.job_id || "") + " started.", "success");
|
|
2248
|
-
upsertInpaintHistoryItem(makePendingInpaintHistoryItem(payload));
|
|
2249
|
-
await pollInpaintJob(payload.job_id);
|
|
2250
|
-
await loadInpaintHistory();
|
|
2251
|
-
} catch (error) {
|
|
2252
|
-
console.error(error);
|
|
2253
|
-
setStatus(error instanceof Error ? error.message : "Unable to create image edit.", "error");
|
|
2254
|
-
} finally {
|
|
2255
|
-
setSubmitLoading(false);
|
|
2256
|
-
uploadButton.disabled = false;
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
renderInpaintControls();
|
|
2261
|
-
void loadInpaintProviders();
|
|
2262
|
-
initializeFromUrl();
|
|
2263
|
-
void loadInpaintHistory();
|
|
2264
|
-
uploadButton.addEventListener("click", () => fileInput.click());
|
|
2265
|
-
historyRefresh.addEventListener("click", () => {
|
|
2266
|
-
void loadInpaintHistory();
|
|
2267
|
-
});
|
|
2268
|
-
historyList.addEventListener("pointerdown", (event) => {
|
|
2269
|
-
const target = event.target;
|
|
2270
|
-
if (target instanceof Element && target.closest("[data-history-confirm]")) {
|
|
2271
|
-
event.stopPropagation();
|
|
2272
|
-
}
|
|
2273
|
-
});
|
|
2274
|
-
historyList.addEventListener("click", (event) => {
|
|
2275
|
-
const target = event.target;
|
|
2276
|
-
if (!(target instanceof Element)) return;
|
|
2277
|
-
const confirmPopover = target.closest("[data-history-confirm]");
|
|
2278
|
-
if (confirmPopover instanceof HTMLElement) {
|
|
2279
|
-
event.stopPropagation();
|
|
2280
|
-
}
|
|
2281
|
-
const previewButton = target.closest("[data-history-preview]");
|
|
2282
|
-
if (previewButton instanceof HTMLElement) {
|
|
2283
|
-
const key = previewButton.dataset.historyPreview || "";
|
|
2284
|
-
const item = inpaintHistoryItems.find((entry) => entry.key === key);
|
|
2285
|
-
if (!item) return;
|
|
2286
|
-
openImagePreview(item.url, item.createdAt ? new Date(item.createdAt).toLocaleString() : "Image edit output", imageDownloadName("image-edit-history", item.createdAt));
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
const copyButton = target.closest("[data-history-copy]");
|
|
2290
|
-
if (copyButton instanceof HTMLElement) {
|
|
2291
|
-
const key = copyButton.dataset.historyCopy || "";
|
|
2292
|
-
const item = inpaintHistoryItems.find((entry) => entry.key === key);
|
|
2293
|
-
if (!item) return;
|
|
2294
|
-
void copyText(item.url).then((copied) => {
|
|
2295
|
-
copyButton.setAttribute("title", copied ? "Copied" : "Copy failed");
|
|
2296
|
-
window.setTimeout(() => copyButton.setAttribute("title", "Copy URL"), 1200);
|
|
2297
|
-
});
|
|
2298
|
-
return;
|
|
2299
|
-
}
|
|
2300
|
-
const checkButton = target.closest("[data-history-check]");
|
|
2301
|
-
if (checkButton instanceof HTMLButtonElement) {
|
|
2302
|
-
void checkInpaintJobStatus(checkButton.dataset.historyCheck || "", checkButton);
|
|
2303
|
-
return;
|
|
2304
|
-
}
|
|
2305
|
-
const removeButton = target.closest("[data-history-remove]");
|
|
2306
|
-
if (removeButton instanceof HTMLElement) {
|
|
2307
|
-
const key = removeButton.dataset.historyRemove || "";
|
|
2308
|
-
const confirm = historyList.querySelector('[data-history-confirm="' + CSS.escape(key) + '"]');
|
|
2309
|
-
const shouldOpen = !(confirm instanceof HTMLElement) || confirm.hidden;
|
|
2310
|
-
closeInpaintHistoryConfirms();
|
|
2311
|
-
if (confirm instanceof HTMLElement) {
|
|
2312
|
-
confirm.hidden = !shouldOpen;
|
|
2313
|
-
}
|
|
2314
|
-
removeButton.setAttribute("aria-expanded", shouldOpen ? "true" : "false");
|
|
2315
|
-
return;
|
|
2316
|
-
}
|
|
2317
|
-
const cancelButton = target.closest("[data-history-cancel]");
|
|
2318
|
-
if (cancelButton instanceof HTMLElement) {
|
|
2319
|
-
const confirm = cancelButton.closest("[data-history-confirm]");
|
|
2320
|
-
if (confirm instanceof HTMLElement) confirm.hidden = true;
|
|
2321
|
-
return;
|
|
2322
|
-
}
|
|
2323
|
-
const confirmRemove = target.closest("[data-history-confirm-remove]");
|
|
2324
|
-
if (confirmRemove instanceof HTMLElement) {
|
|
2325
|
-
const key = confirmRemove.dataset.historyConfirmRemove || "";
|
|
2326
|
-
if (key) {
|
|
2327
|
-
hiddenInpaintHistoryKeys.add(key);
|
|
2328
|
-
renderInpaintHistory();
|
|
2329
|
-
}
|
|
2330
|
-
return;
|
|
2331
|
-
}
|
|
2332
|
-
});
|
|
2333
|
-
resultOpenButton.addEventListener("click", () => {
|
|
2334
|
-
if (!resultImage.currentSrc && !resultImage.src) return;
|
|
2335
|
-
openImagePreview(resultImage.currentSrc || resultImage.src, "Image edit result", imageDownloadName("image-edit-result"));
|
|
2336
|
-
});
|
|
2337
|
-
lightboxCopy.addEventListener("click", () => {
|
|
2338
|
-
const url = lightboxCopy.dataset.copyUrl || lightboxImage.currentSrc || lightboxImage.src || "";
|
|
2339
|
-
if (!url) return;
|
|
2340
|
-
void copyText(url).then((copied) => {
|
|
2341
|
-
lightboxCopy.setAttribute("title", copied ? "Copied" : "Copy failed");
|
|
2342
|
-
window.setTimeout(() => lightboxCopy.setAttribute("title", "Copy URL"), 1200);
|
|
2343
|
-
});
|
|
2344
|
-
});
|
|
2345
|
-
lightboxClose.addEventListener("click", () => closeImagePreview());
|
|
2346
|
-
lightbox.addEventListener("click", (event) => {
|
|
2347
|
-
if (event.target === lightbox) closeImagePreview();
|
|
2348
|
-
});
|
|
2349
|
-
startNewButton.addEventListener("click", () => {
|
|
2350
|
-
tracerInput.value = makeTracerId();
|
|
2351
|
-
clearInpaintUrlParams();
|
|
2352
|
-
resetWorkspace();
|
|
2353
|
-
void loadInpaintHistory();
|
|
2354
|
-
});
|
|
2355
|
-
tracerInput.addEventListener("input", () => {
|
|
2356
|
-
queueInpaintHistoryReload();
|
|
2357
|
-
});
|
|
2358
|
-
fileInput.addEventListener("change", () => {
|
|
2359
|
-
void loadSourceFile(fileInput.files && fileInput.files[0]);
|
|
2360
|
-
fileInput.value = "";
|
|
2361
|
-
});
|
|
2362
|
-
inpaintAttachmentInput.addEventListener("change", () => {
|
|
2363
|
-
void addAttachmentFiles(inpaintAttachmentInput.files);
|
|
2364
|
-
inpaintAttachmentInput.value = "";
|
|
2365
|
-
});
|
|
2366
|
-
inpaintGlobalAttachments.addEventListener("click", (event) => {
|
|
2367
|
-
const target = event.target;
|
|
2368
|
-
if (!(target instanceof Element)) return;
|
|
2369
|
-
const removeColor = target.closest("[data-editor-inpaint-remove-color]");
|
|
2370
|
-
if (removeColor instanceof HTMLElement && removeColor.dataset.editorInpaintRemoveColor) {
|
|
2371
|
-
removeInpaintColor(removeColor.dataset.editorInpaintRemoveColor);
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2374
|
-
const remove = target.closest("[data-editor-inpaint-remove-attachment]");
|
|
2375
|
-
if (remove instanceof HTMLElement) {
|
|
2376
|
-
inpaintAttachments = inpaintAttachments.filter((attachment) => attachment.id !== remove.dataset.editorInpaintRemoveAttachment);
|
|
2377
|
-
renderAllAttachmentChips();
|
|
2378
|
-
}
|
|
2379
|
-
});
|
|
2380
|
-
document.querySelector('[data-editor-inpaint-attach-scope="global"]')?.addEventListener("click", () => {
|
|
2381
|
-
openAttachmentPicker("global");
|
|
2382
|
-
});
|
|
2383
|
-
inpaintGlobalPrompt.addEventListener("paste", (event) => {
|
|
2384
|
-
handleAttachmentPaste(event, "global");
|
|
2385
|
-
});
|
|
2386
|
-
inpaintUndo.addEventListener("click", () => {
|
|
2387
|
-
const stroke = inpaintStrokes.pop();
|
|
2388
|
-
if (stroke) redoStrokes.push(stroke);
|
|
2389
|
-
redrawInpaintMask();
|
|
2390
|
-
setStatus("", "");
|
|
2391
|
-
});
|
|
2392
|
-
inpaintRedo.addEventListener("click", () => {
|
|
2393
|
-
const stroke = redoStrokes.pop();
|
|
2394
|
-
if (stroke) inpaintStrokes.push(stroke);
|
|
2395
|
-
redrawInpaintMask();
|
|
2396
|
-
setStatus("", "");
|
|
2397
|
-
});
|
|
2398
|
-
inpaintClear.addEventListener("click", () => {
|
|
2399
|
-
resetMask();
|
|
2400
|
-
setStatus("", "");
|
|
2401
|
-
});
|
|
2402
|
-
inpaintBrush.addEventListener("input", () => {
|
|
2403
|
-
inpaintBrushLabel.textContent = inpaintBrush.value;
|
|
2404
|
-
updateBrushCursorSize();
|
|
2405
|
-
});
|
|
2406
|
-
inpaintColorPicker.addEventListener("input", () => {
|
|
2407
|
-
activeInpaintColor = normalizeHexColor(inpaintColorPicker.value);
|
|
2408
|
-
renderInpaintControls();
|
|
2409
|
-
});
|
|
2410
|
-
inpaintAddColor.addEventListener("click", () => addInpaintColor(inpaintColorPicker.value));
|
|
2411
|
-
inpaintPrompts.addEventListener("click", (event) => {
|
|
2412
|
-
const target = event.target;
|
|
2413
|
-
if (!(target instanceof Element)) return;
|
|
2414
|
-
const remove = target.closest("[data-editor-inpaint-remove-attachment]");
|
|
2415
|
-
if (remove instanceof HTMLElement) {
|
|
2416
|
-
inpaintAttachments = inpaintAttachments.filter((attachment) => attachment.id !== remove.dataset.editorInpaintRemoveAttachment);
|
|
2417
|
-
renderAllAttachmentChips();
|
|
2418
|
-
return;
|
|
2419
|
-
}
|
|
2420
|
-
const attach = target.closest("[data-editor-inpaint-attach-scope]");
|
|
2421
|
-
if (attach instanceof HTMLElement && attach.dataset.editorInpaintAttachScope === "color" && attach.dataset.color) {
|
|
2422
|
-
openAttachmentPicker("color", attach.dataset.color);
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
const row = target.closest(".editor-inpaint-color-row");
|
|
2426
|
-
if (row instanceof HTMLElement && row.dataset.color) selectInpaintColor(row.dataset.color);
|
|
2427
|
-
});
|
|
2428
|
-
inpaintPrompts.addEventListener("focusin", (event) => {
|
|
2429
|
-
const target = event.target;
|
|
2430
|
-
if (!(target instanceof Element)) return;
|
|
2431
|
-
const row = target.closest(".editor-inpaint-color-row");
|
|
2432
|
-
if (row instanceof HTMLElement && row.dataset.color) selectInpaintColor(row.dataset.color);
|
|
2433
|
-
});
|
|
2434
|
-
inpaintPrompts.addEventListener("input", (event) => {
|
|
2435
|
-
const target = event.target;
|
|
2436
|
-
if (target instanceof HTMLTextAreaElement) updateInpaintPrompt(target.dataset.color || "", target.value);
|
|
2437
|
-
});
|
|
2438
|
-
inpaintPrompts.addEventListener("paste", (event) => {
|
|
2439
|
-
const target = event.target;
|
|
2440
|
-
if (target instanceof HTMLTextAreaElement && target.dataset.color) {
|
|
2441
|
-
handleAttachmentPaste(event, "color", target.dataset.color);
|
|
2442
|
-
}
|
|
2443
|
-
});
|
|
2444
|
-
inpaintCanvas.addEventListener("pointerenter", (event) => updateBrushCursorPosition(event));
|
|
2445
|
-
inpaintCanvas.addEventListener("pointerdown", (event) => {
|
|
2446
|
-
updateBrushCursorPosition(event);
|
|
2447
|
-
beginInpaintStroke(event);
|
|
2448
|
-
});
|
|
2449
|
-
inpaintCanvas.addEventListener("pointermove", (event) => {
|
|
2450
|
-
updateBrushCursorPosition(event);
|
|
2451
|
-
continueInpaintStroke(event);
|
|
2452
|
-
});
|
|
2453
|
-
inpaintCanvas.addEventListener("pointerup", (event) => endInpaintStroke(event));
|
|
2454
|
-
inpaintCanvas.addEventListener("pointercancel", (event) => {
|
|
2455
|
-
endInpaintStroke(event);
|
|
2456
|
-
hideBrushCursor();
|
|
2457
|
-
});
|
|
2458
|
-
inpaintCanvas.addEventListener("pointerleave", (event) => {
|
|
2459
|
-
endInpaintStroke(event);
|
|
2460
|
-
hideBrushCursor();
|
|
2461
|
-
});
|
|
2462
|
-
inpaintSubmit.addEventListener("click", () => {
|
|
2463
|
-
void submitInpaintJob();
|
|
2464
|
-
});
|
|
2465
|
-
settingsButton.addEventListener("click", () => {
|
|
2466
|
-
settingsPopover.hidden = !settingsPopover.hidden;
|
|
2467
|
-
if (!settingsPopover.hidden) tracerInput.focus();
|
|
2468
|
-
});
|
|
2469
|
-
window.addEventListener("click", (event) => {
|
|
2470
|
-
const target = event.target;
|
|
2471
|
-
if (!(target instanceof Node)) return;
|
|
2472
|
-
const isHistoryConfirmTarget = target instanceof Element && Boolean(target.closest("[data-history-confirm]"));
|
|
2473
|
-
if (!historyList.contains(target) && !isHistoryConfirmTarget) {
|
|
2474
|
-
closeInpaintHistoryConfirms();
|
|
2475
|
-
}
|
|
2476
|
-
if (!settingsPopover.hidden && !settingsPopover.contains(target) && !settingsButton.contains(target)) {
|
|
2477
|
-
settingsPopover.hidden = true;
|
|
2478
|
-
}
|
|
2479
|
-
});
|
|
2480
|
-
window.addEventListener("resize", () => syncInpaintCanvasSize());
|
|
2481
|
-
window.addEventListener("keydown", (event) => {
|
|
2482
|
-
if (event.key === "Escape" && lightbox.getAttribute("data-open") === "true") {
|
|
2483
|
-
closeImagePreview();
|
|
2484
|
-
}
|
|
2485
|
-
});
|
|
2486
|
-
})();
|
|
2487
|
-
</script>
|
|
2488
|
-
`
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
//# sourceMappingURL=template-editor-pages.js.map
|