@mevdragon/vidfarm-devcli 0.5.3 → 0.6.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 +3 -3
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +1184 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
- package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +15 -0
- package/dist/src/account-pages-legacy.js +9396 -0
- package/dist/src/account-pages.js +61 -0
- package/dist/src/app.js +14378 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/composition-runtime.js +613 -0
- package/dist/src/config.js +166 -0
- package/dist/src/context.js +447 -0
- package/dist/src/dev-app-legacy.js +739 -0
- package/dist/src/dev-app.js +6 -0
- package/dist/src/domain.js +2 -0
- package/dist/src/editor-chat-history.js +82 -0
- package/dist/src/editor-chat.js +449 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/frontend/debug.js +71 -0
- package/dist/src/frontend/flockposter-cache-store.js +124 -0
- package/dist/src/frontend/homepage-client.js +182 -0
- package/dist/src/frontend/homepage-shared.js +4 -0
- package/dist/src/frontend/homepage-store.js +28 -0
- package/dist/src/frontend/homepage-view.js +547 -0
- package/dist/src/frontend/page-runtime-client.js +132 -0
- package/dist/src/frontend/page-runtime-store.js +9 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/template-editor-chat.js +3960 -0
- package/dist/src/help-page.js +346 -0
- package/dist/src/homepage.js +1235 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/index.js +16 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/page-runtime.js +2 -0
- package/dist/src/page-shell.js +1381 -0
- package/dist/src/primitive-context.js +357 -0
- package/dist/src/primitive-registry.js +2436 -0
- package/dist/src/primitive-sdk.js +4 -0
- package/dist/src/primitives/hyperframes-media.js +108 -0
- package/dist/src/react-page-shell.js +35 -0
- package/dist/src/ready-post-schedule-component.js +1540 -0
- package/dist/src/registry.js +296 -0
- package/dist/src/runtime.js +35 -0
- package/dist/src/services/api-call-history.js +249 -0
- package/dist/src/services/auth.js +152 -0
- package/dist/src/services/billing-pricing.js +39 -0
- package/dist/src/services/billing.js +228 -0
- package/dist/src/services/cast.js +127 -0
- package/dist/src/services/chat-threads.js +92 -0
- package/dist/src/services/composition-sanitize.js +124 -0
- package/dist/src/services/composition-watch.js +79 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-manifest.js +42 -0
- package/dist/src/services/ghostcut.js +179 -0
- package/dist/src/services/hyperframes.js +2307 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-logs.js +197 -0
- package/dist/src/services/jobs.js +136 -0
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +766 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/providers.js +2926 -0
- package/dist/src/services/rate-limits.js +262 -0
- package/dist/src/services/serverless-auth.js +382 -0
- package/dist/src/services/serverless-jobs.js +1082 -0
- package/dist/src/services/serverless-provider-keys.js +409 -0
- package/dist/src/services/serverless-records.js +1385 -0
- package/dist/src/services/serverless-template-configs.js +75 -0
- package/dist/src/services/storage.js +383 -0
- package/dist/src/services/template-certification.js +413 -0
- package/dist/src/services/template-loader.js +99 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-sources.js +1017 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/webhooks.js +62 -0
- package/dist/src/template-editor-pages.js +2576 -0
- package/dist/src/template-editor-shell.js +2840 -0
- package/dist/src/template-sdk.js +4 -0
- package/dist/src/worker.js +17 -0
- package/package.json +6 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
- package/src/assets/SELLING_WITH_HOOKS.md +377 -0
- package/src/assets/SELLING_WITH_VSLS.md +606 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import { escapeJsonForHtml, renderBrandLockup, renderPageShell } from "./page-shell.js";
|
|
2
|
+
export function renderDevApp(input) {
|
|
3
|
+
const boot = escapeJsonForHtml({
|
|
4
|
+
templates: input.templates,
|
|
5
|
+
environment: input.environment,
|
|
6
|
+
apiBasePath: "/api/v1"
|
|
7
|
+
});
|
|
8
|
+
return renderPageShell({
|
|
9
|
+
title: "Vidfarm Developer Console",
|
|
10
|
+
mainClass: "is-wide",
|
|
11
|
+
body: `
|
|
12
|
+
<section class="frame console-frame">
|
|
13
|
+
<header class="topbar">
|
|
14
|
+
${renderBrandLockup()}
|
|
15
|
+
<div class="topbar-actions">
|
|
16
|
+
<span class="pill is-active">Async First</span>
|
|
17
|
+
<span class="pill">${input.environment}</span>
|
|
18
|
+
</div>
|
|
19
|
+
</header>
|
|
20
|
+
<div class="frame-body">
|
|
21
|
+
<section class="page-hero">
|
|
22
|
+
<div class="surface-hero stack-lg">
|
|
23
|
+
<span class="kicker">Console</span>
|
|
24
|
+
<div class="stack">
|
|
25
|
+
<h1>Developer console.</h1>
|
|
26
|
+
<p class="lede">Run timeline compositions, render jobs, and traces.</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<aside class="metric-grid">
|
|
30
|
+
<section class="surface metric">
|
|
31
|
+
<div class="eyebrow">Environment</div>
|
|
32
|
+
<div class="display stat-value" id="envLabel">${input.environment}</div>
|
|
33
|
+
</section>
|
|
34
|
+
<section class="surface metric">
|
|
35
|
+
<div class="eyebrow">Composition Recipes</div>
|
|
36
|
+
<div class="display stat-value" id="templateCount">${input.templates.length}</div>
|
|
37
|
+
</section>
|
|
38
|
+
<section class="surface metric">
|
|
39
|
+
<div class="eyebrow">Mode</div>
|
|
40
|
+
<div class="display stat-value">Async First</div>
|
|
41
|
+
</section>
|
|
42
|
+
</aside>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section class="split-grid console-grid">
|
|
46
|
+
<aside class="stack">
|
|
47
|
+
<section class="surface page-section">
|
|
48
|
+
<div class="stack" style="gap:8px;">
|
|
49
|
+
<div class="pill">Access</div>
|
|
50
|
+
<h2>Developer Login</h2>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="label-group">
|
|
53
|
+
<label for="email">Email</label>
|
|
54
|
+
<input id="email" placeholder="dev@example.com" />
|
|
55
|
+
</div>
|
|
56
|
+
<div class="field-grid cols-2">
|
|
57
|
+
<button id="requestOtp">Request OTP</button>
|
|
58
|
+
<button id="clearSession" class="secondary">Clear Session</button>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="label-group">
|
|
61
|
+
<label for="otp">6 Digit OTP</label>
|
|
62
|
+
<input id="otp" placeholder="123456" />
|
|
63
|
+
</div>
|
|
64
|
+
<div class="label-group">
|
|
65
|
+
<label for="name">Display Name</label>
|
|
66
|
+
<input id="name" placeholder="Composition Dev" />
|
|
67
|
+
</div>
|
|
68
|
+
<button id="verifyOtp">Verify And Issue API Key</button>
|
|
69
|
+
<div class="footer-note" id="sessionSummary">No active session.</div>
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<section class="surface page-section">
|
|
73
|
+
<div class="stack" style="gap:8px;">
|
|
74
|
+
<div class="pill">Providers</div>
|
|
75
|
+
<h2>Customer AI Keys</h2>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="field-grid cols-2">
|
|
78
|
+
<div class="label-group">
|
|
79
|
+
<label for="provider">Provider</label>
|
|
80
|
+
<select id="provider">
|
|
81
|
+
<option value="openai">openai</option>
|
|
82
|
+
<option value="openrouter">openrouter</option>
|
|
83
|
+
<option value="gemini">gemini</option>
|
|
84
|
+
<option value="perplexity">perplexity</option>
|
|
85
|
+
</select>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="label-group">
|
|
89
|
+
<label for="providerLabel">Label</label>
|
|
90
|
+
<input id="providerLabel" placeholder="Primary key" />
|
|
91
|
+
</div>
|
|
92
|
+
<div class="label-group">
|
|
93
|
+
<label for="providerSecret">Secret</label>
|
|
94
|
+
<input id="providerSecret" placeholder="sk-..." />
|
|
95
|
+
</div>
|
|
96
|
+
<div class="stack" style="gap:10px;">
|
|
97
|
+
<button id="saveProviderKey">Store Provider Key</button>
|
|
98
|
+
<button id="refreshProviderKeys" class="secondary">Refresh Key Inventory</button>
|
|
99
|
+
</div>
|
|
100
|
+
<pre id="providerKeysView">[]</pre>
|
|
101
|
+
</section>
|
|
102
|
+
</aside>
|
|
103
|
+
|
|
104
|
+
<main class="workspace">
|
|
105
|
+
<section class="surface page-section">
|
|
106
|
+
<div class="stack" style="gap:8px;">
|
|
107
|
+
<div class="pill">Registry</div>
|
|
108
|
+
<h2>Composition Recipes</h2>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="template-grid" id="templates"></div>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section class="surface page-section">
|
|
114
|
+
<div class="stack" style="gap:8px;">
|
|
115
|
+
<div class="pill">Timeline Control</div>
|
|
116
|
+
<h2 id="selectedTemplateTitle">Select a composition recipe</h2>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="ribbon">
|
|
119
|
+
<span class="pill">Selected <strong id="selectedTemplateId">None</strong></span>
|
|
120
|
+
<span class="pill">Action <strong id="selectedOperationName">None</strong></span>
|
|
121
|
+
<span class="pill">Tracer <strong id="selectedTracer">Pending</strong></span>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="field-grid cols-3">
|
|
125
|
+
<div class="label-group">
|
|
126
|
+
<label for="operationSelect">Action</label>
|
|
127
|
+
<select id="operationSelect"></select>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="label-group">
|
|
130
|
+
<label for="tracerInput">Tracer</label>
|
|
131
|
+
<input id="tracerInput" placeholder="dev-run-001" />
|
|
132
|
+
</div>
|
|
133
|
+
<div class="label-group">
|
|
134
|
+
<label for="webhookInput">Webhook URL</label>
|
|
135
|
+
<input id="webhookInput" placeholder="Optional https://..." />
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="label-group">
|
|
140
|
+
<label for="configEditor">Composition Config JSON</label>
|
|
141
|
+
<textarea id="configEditor"></textarea>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="field-grid cols-2">
|
|
144
|
+
<button id="saveConfig">Save Config</button>
|
|
145
|
+
<button id="loadTemplate" class="secondary">Reload Composition Metadata</button>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="label-group">
|
|
149
|
+
<label for="payloadEditor">Render Payload JSON</label>
|
|
150
|
+
<textarea id="payloadEditor"></textarea>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="field-grid cols-2">
|
|
153
|
+
<button id="launchJob">Launch Render Job</button>
|
|
154
|
+
<button id="loadJobs" class="secondary">Refresh Renders</button>
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
157
|
+
|
|
158
|
+
<section class="surface page-section">
|
|
159
|
+
<div class="stack" style="gap:8px;">
|
|
160
|
+
<div class="pill">Render Jobs</div>
|
|
161
|
+
<h2>Render Board</h2>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="job-grid" id="jobs"></div>
|
|
164
|
+
</section>
|
|
165
|
+
|
|
166
|
+
<section class="surface page-section">
|
|
167
|
+
<div class="stack" style="gap:8px;">
|
|
168
|
+
<div class="pill">Execution Trace</div>
|
|
169
|
+
<h2>Live Timeline</h2>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="timeline" id="timeline"></div>
|
|
172
|
+
<div class="footer-note" id="jobSummary">No render selected.</div>
|
|
173
|
+
</section>
|
|
174
|
+
|
|
175
|
+
<section class="surface page-section">
|
|
176
|
+
<div class="stack" style="gap:8px;">
|
|
177
|
+
<div class="pill">Response View</div>
|
|
178
|
+
<h2>Inspector</h2>
|
|
179
|
+
</div>
|
|
180
|
+
<pre id="inspector">{}</pre>
|
|
181
|
+
</section>
|
|
182
|
+
</main>
|
|
183
|
+
</section>
|
|
184
|
+
</div>
|
|
185
|
+
</section>
|
|
186
|
+
|
|
187
|
+
<script>
|
|
188
|
+
const BOOT = ${boot};
|
|
189
|
+
const state = {
|
|
190
|
+
baseUrl: window.location.origin,
|
|
191
|
+
apiBasePath: BOOT.apiBasePath,
|
|
192
|
+
session: loadSession(),
|
|
193
|
+
templates: BOOT.templates,
|
|
194
|
+
selectedTemplateId: BOOT.templates[0]?.id ?? null,
|
|
195
|
+
selectedOperation: null,
|
|
196
|
+
selectedJobId: null,
|
|
197
|
+
jobs: [],
|
|
198
|
+
jobPollTimer: null
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const STANDARD_JOB_POLL_MS = 60 * 1000;
|
|
202
|
+
const BATCH_JOB_POLL_MS = 5 * 60 * 1000;
|
|
203
|
+
const BATCH_JOB_THRESHOLD = 5;
|
|
204
|
+
|
|
205
|
+
const els = {
|
|
206
|
+
email: document.getElementById("email"),
|
|
207
|
+
otp: document.getElementById("otp"),
|
|
208
|
+
name: document.getElementById("name"),
|
|
209
|
+
requestOtp: document.getElementById("requestOtp"),
|
|
210
|
+
verifyOtp: document.getElementById("verifyOtp"),
|
|
211
|
+
clearSession: document.getElementById("clearSession"),
|
|
212
|
+
sessionSummary: document.getElementById("sessionSummary"),
|
|
213
|
+
provider: document.getElementById("provider"),
|
|
214
|
+
providerLabel: document.getElementById("providerLabel"),
|
|
215
|
+
providerSecret: document.getElementById("providerSecret"),
|
|
216
|
+
saveProviderKey: document.getElementById("saveProviderKey"),
|
|
217
|
+
refreshProviderKeys: document.getElementById("refreshProviderKeys"),
|
|
218
|
+
providerKeysView: document.getElementById("providerKeysView"),
|
|
219
|
+
templates: document.getElementById("templates"),
|
|
220
|
+
operationSelect: document.getElementById("operationSelect"),
|
|
221
|
+
tracerInput: document.getElementById("tracerInput"),
|
|
222
|
+
webhookInput: document.getElementById("webhookInput"),
|
|
223
|
+
selectedTemplateTitle: document.getElementById("selectedTemplateTitle"),
|
|
224
|
+
selectedTemplateId: document.getElementById("selectedTemplateId"),
|
|
225
|
+
selectedOperationName: document.getElementById("selectedOperationName"),
|
|
226
|
+
selectedTracer: document.getElementById("selectedTracer"),
|
|
227
|
+
configEditor: document.getElementById("configEditor"),
|
|
228
|
+
payloadEditor: document.getElementById("payloadEditor"),
|
|
229
|
+
saveConfig: document.getElementById("saveConfig"),
|
|
230
|
+
loadTemplate: document.getElementById("loadTemplate"),
|
|
231
|
+
launchJob: document.getElementById("launchJob"),
|
|
232
|
+
loadJobs: document.getElementById("loadJobs"),
|
|
233
|
+
jobs: document.getElementById("jobs"),
|
|
234
|
+
timeline: document.getElementById("timeline"),
|
|
235
|
+
inspector: document.getElementById("inspector"),
|
|
236
|
+
jobSummary: document.getElementById("jobSummary")
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function loadSession() {
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(localStorage.getItem("vidfarm-dev-session") || "null");
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function saveSession(session) {
|
|
248
|
+
state.session = session;
|
|
249
|
+
if (session) {
|
|
250
|
+
localStorage.setItem("vidfarm-dev-session", JSON.stringify(session));
|
|
251
|
+
} else {
|
|
252
|
+
localStorage.removeItem("vidfarm-dev-session");
|
|
253
|
+
}
|
|
254
|
+
renderSession();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function headers() {
|
|
258
|
+
if (!state.session) {
|
|
259
|
+
throw new Error("No active session. Verify OTP first.");
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
"content-type": "application/json",
|
|
263
|
+
"vidfarm-api-key": state.session.apiKey
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function request(path, options = {}) {
|
|
268
|
+
const response = await fetch(state.baseUrl + state.apiBasePath + path, options);
|
|
269
|
+
const data = await response.json().catch(() => ({}));
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
throw new Error(data.error || JSON.stringify(data));
|
|
272
|
+
}
|
|
273
|
+
els.inspector.textContent = JSON.stringify(data, null, 2);
|
|
274
|
+
return data;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function renderSession() {
|
|
278
|
+
if (!state.session) {
|
|
279
|
+
els.sessionSummary.textContent = "No active session.";
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const developerLabel = state.session.customer.isDeveloper ? "developer" : "standard";
|
|
283
|
+
els.sessionSummary.textContent =
|
|
284
|
+
"Active: " + state.session.customer.email + " • " + state.session.customer.id + " • " + developerLabel;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getSelectedTemplate() {
|
|
288
|
+
return state.templates.find((template) => template.id === state.selectedTemplateId) || null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getTemplateDefaults(templateId, operation) {
|
|
292
|
+
if (templateId === "template_0000" || templateId === "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db") {
|
|
293
|
+
const config = {
|
|
294
|
+
defaultProvider: "openai",
|
|
295
|
+
textModel: "gpt-4.1-mini",
|
|
296
|
+
imageModel: "gpt-image-1",
|
|
297
|
+
renderCompositionId: "template-0000"
|
|
298
|
+
};
|
|
299
|
+
const payloads = {
|
|
300
|
+
generate: {
|
|
301
|
+
slides: [
|
|
302
|
+
["a cinematic founder working late in a warm studio", "Build the pipeline before you scale the content"],
|
|
303
|
+
["a crisp top-down desk with storyboard pages and coffee", "Use layouts that leave clean space for exact text"],
|
|
304
|
+
["a polished vertical product montage with dramatic lighting", "Render to 9:16 and ship straight to TikTok"]
|
|
305
|
+
],
|
|
306
|
+
secondsPerSlide: 4
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
return {
|
|
310
|
+
config,
|
|
311
|
+
payload: payloads[operation] || {}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { config: {}, payload: {} };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function renderTemplates() {
|
|
318
|
+
els.templates.innerHTML = "";
|
|
319
|
+
state.templates.forEach((template) => {
|
|
320
|
+
const card = document.createElement("button");
|
|
321
|
+
card.className = "template-card" + (template.id === state.selectedTemplateId ? " active" : "");
|
|
322
|
+
card.innerHTML = \`
|
|
323
|
+
<div class="template-title">\${template.id}</div>
|
|
324
|
+
<div class="template-desc">\${template.description || ""}</div>
|
|
325
|
+
<div class="operation-list">
|
|
326
|
+
\${(template.operations || []).map((op) => \`<span class="op">\${op.name}</span>\`).join("")}
|
|
327
|
+
</div>
|
|
328
|
+
\`;
|
|
329
|
+
card.addEventListener("click", () => {
|
|
330
|
+
state.selectedTemplateId = template.id;
|
|
331
|
+
state.selectedOperation = template.operations?.[0]?.name || null;
|
|
332
|
+
hydrateTemplateEditors();
|
|
333
|
+
renderTemplates();
|
|
334
|
+
});
|
|
335
|
+
els.templates.appendChild(card);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function hydrateTemplateEditors() {
|
|
340
|
+
const template = getSelectedTemplate();
|
|
341
|
+
if (!template) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
els.selectedTemplateTitle.textContent = template.description || template.id;
|
|
345
|
+
els.selectedTemplateId.textContent = template.id;
|
|
346
|
+
els.operationSelect.innerHTML = "";
|
|
347
|
+
(template.operations || []).forEach((op) => {
|
|
348
|
+
const option = document.createElement("option");
|
|
349
|
+
option.value = op.name;
|
|
350
|
+
option.textContent = op.name;
|
|
351
|
+
els.operationSelect.appendChild(option);
|
|
352
|
+
});
|
|
353
|
+
if (!state.selectedOperation) {
|
|
354
|
+
state.selectedOperation = template.operations?.[0]?.name || null;
|
|
355
|
+
}
|
|
356
|
+
els.operationSelect.value = state.selectedOperation || "";
|
|
357
|
+
const defaults = getTemplateDefaults(template.id, state.selectedOperation);
|
|
358
|
+
els.configEditor.value = JSON.stringify(defaults.config, null, 2);
|
|
359
|
+
els.payloadEditor.value = JSON.stringify(defaults.payload, null, 2);
|
|
360
|
+
els.tracerInput.value = "dev-" + Math.random().toString(36).slice(2, 9);
|
|
361
|
+
renderOperationBanner();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function renderOperationBanner() {
|
|
365
|
+
els.selectedOperationName.textContent = state.selectedOperation || "None";
|
|
366
|
+
els.selectedTracer.textContent = els.tracerInput.value || "Pending";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function renderJobs() {
|
|
370
|
+
els.jobs.innerHTML = "";
|
|
371
|
+
state.jobs.forEach((job) => {
|
|
372
|
+
const card = document.createElement("button");
|
|
373
|
+
card.className = "job-card";
|
|
374
|
+
card.innerHTML = \`
|
|
375
|
+
<div style="display:flex;justify-content:space-between;gap:12px;align-items:center;">
|
|
376
|
+
<strong>\${job.operationName || job.operation_name}</strong>
|
|
377
|
+
<span class="status \${job.status}">\${job.status}</span>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="muted" style="margin-top:8px;">\${job.id || job.job_id}</div>
|
|
380
|
+
<div class="muted" style="margin-top:8px;">Tracer: \${job.tracer}</div>
|
|
381
|
+
\`;
|
|
382
|
+
card.addEventListener("click", async () => {
|
|
383
|
+
state.selectedJobId = job.id || job.job_id;
|
|
384
|
+
await refreshJobDetail();
|
|
385
|
+
});
|
|
386
|
+
els.jobs.appendChild(card);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function refreshProviderKeys() {
|
|
391
|
+
const data = await request("/user/me/provider-keys", { headers: headers() });
|
|
392
|
+
els.providerKeysView.textContent = JSON.stringify(data.provider_keys, null, 2);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function refreshJobs() {
|
|
396
|
+
const data = await request("/user/me/jobs", { headers: headers() });
|
|
397
|
+
state.jobs = data.jobs || [];
|
|
398
|
+
renderJobs();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function clearJobPollTimer() {
|
|
402
|
+
if (state.jobPollTimer) {
|
|
403
|
+
clearTimeout(state.jobPollTimer);
|
|
404
|
+
state.jobPollTimer = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function getSelectedJobRecord() {
|
|
409
|
+
return state.jobs.find((job) => (job.id || job.job_id) === state.selectedJobId) || null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function getJobPollIntervalMs() {
|
|
413
|
+
const selectedJob = getSelectedJobRecord();
|
|
414
|
+
const selectedTracer = selectedJob?.tracer;
|
|
415
|
+
if (!selectedTracer) {
|
|
416
|
+
return STANDARD_JOB_POLL_MS;
|
|
417
|
+
}
|
|
418
|
+
const activeTracerJobs = state.jobs.filter((job) => {
|
|
419
|
+
const status = job.status;
|
|
420
|
+
return job.tracer === selectedTracer && (status === "queued" || status === "running");
|
|
421
|
+
}).length;
|
|
422
|
+
return activeTracerJobs >= BATCH_JOB_THRESHOLD ? BATCH_JOB_POLL_MS : STANDARD_JOB_POLL_MS;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function scheduleJobPoll() {
|
|
426
|
+
clearJobPollTimer();
|
|
427
|
+
state.jobPollTimer = setTimeout(async () => {
|
|
428
|
+
try {
|
|
429
|
+
await refreshJobs();
|
|
430
|
+
await refreshJobDetail();
|
|
431
|
+
} catch (error) {
|
|
432
|
+
els.inspector.textContent = JSON.stringify({ error: error.message }, null, 2);
|
|
433
|
+
}
|
|
434
|
+
}, getJobPollIntervalMs());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function refreshJobDetail() {
|
|
438
|
+
clearJobPollTimer();
|
|
439
|
+
if (!state.selectedJobId || !state.selectedTemplateId) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const [job, logs] = await Promise.all([
|
|
443
|
+
request("/templates/" + state.selectedTemplateId + "/jobs/" + state.selectedJobId, { headers: headers() }),
|
|
444
|
+
request("/templates/" + state.selectedTemplateId + "/jobs/" + state.selectedJobId + "/logs", { headers: headers() })
|
|
445
|
+
]);
|
|
446
|
+
renderTimeline(logs.logs || []);
|
|
447
|
+
els.jobSummary.textContent = "Render " + job.job_id + " is " + job.status + " with progress " + job.progress;
|
|
448
|
+
if (job.status === "queued" || job.status === "running") {
|
|
449
|
+
scheduleJobPoll();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function renderTimeline(logs) {
|
|
454
|
+
els.timeline.innerHTML = "";
|
|
455
|
+
if (!logs.length) {
|
|
456
|
+
els.timeline.innerHTML = "<div class='muted'>No events yet.</div>";
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
logs.forEach((log) => {
|
|
460
|
+
const el = document.createElement("div");
|
|
461
|
+
el.className = "event";
|
|
462
|
+
el.innerHTML = \`
|
|
463
|
+
<div class="event-head">
|
|
464
|
+
<span>\${log.level.toUpperCase()}</span>
|
|
465
|
+
<span>\${new Date(log.createdAt).toLocaleTimeString()}</span>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="event-msg">\${log.message}</div>
|
|
468
|
+
<div class="muted" style="margin-top:6px;">progress: \${log.progress ?? "n/a"}</div>
|
|
469
|
+
\`;
|
|
470
|
+
els.timeline.appendChild(el);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
els.requestOtp.addEventListener("click", async () => {
|
|
475
|
+
const email = els.email.value.trim();
|
|
476
|
+
if (!email) return;
|
|
477
|
+
await request("/user/request-otp", {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "content-type": "application/json" },
|
|
480
|
+
body: JSON.stringify({ email })
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
els.verifyOtp.addEventListener("click", async () => {
|
|
485
|
+
const data = await request("/user/verify-otp", {
|
|
486
|
+
method: "POST",
|
|
487
|
+
headers: { "content-type": "application/json" },
|
|
488
|
+
body: JSON.stringify({
|
|
489
|
+
email: els.email.value.trim(),
|
|
490
|
+
code: els.otp.value.trim(),
|
|
491
|
+
name: els.name.value.trim() || undefined
|
|
492
|
+
})
|
|
493
|
+
});
|
|
494
|
+
saveSession(data);
|
|
495
|
+
await refreshProviderKeys();
|
|
496
|
+
await refreshJobs();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
els.clearSession.addEventListener("click", () => {
|
|
500
|
+
clearJobPollTimer();
|
|
501
|
+
saveSession(null);
|
|
502
|
+
els.providerKeysView.textContent = "[]";
|
|
503
|
+
els.jobs.innerHTML = "";
|
|
504
|
+
els.timeline.innerHTML = "";
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
els.saveProviderKey.addEventListener("click", async () => {
|
|
508
|
+
await request("/user/me/provider-keys", {
|
|
509
|
+
method: "POST",
|
|
510
|
+
headers: headers(),
|
|
511
|
+
body: JSON.stringify({
|
|
512
|
+
provider: els.provider.value,
|
|
513
|
+
label: els.providerLabel.value.trim() || undefined,
|
|
514
|
+
secret: els.providerSecret.value.trim()
|
|
515
|
+
})
|
|
516
|
+
});
|
|
517
|
+
els.providerSecret.value = "";
|
|
518
|
+
await refreshProviderKeys();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
els.refreshProviderKeys.addEventListener("click", refreshProviderKeys);
|
|
522
|
+
|
|
523
|
+
els.operationSelect.addEventListener("change", () => {
|
|
524
|
+
state.selectedOperation = els.operationSelect.value;
|
|
525
|
+
const defaults = getTemplateDefaults(state.selectedTemplateId, state.selectedOperation);
|
|
526
|
+
els.payloadEditor.value = JSON.stringify(defaults.payload, null, 2);
|
|
527
|
+
renderOperationBanner();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
els.tracerInput.addEventListener("input", renderOperationBanner);
|
|
531
|
+
|
|
532
|
+
els.saveConfig.addEventListener("click", async () => {
|
|
533
|
+
if (!state.selectedTemplateId) return;
|
|
534
|
+
await request("/templates/" + state.selectedTemplateId + "/config", {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: headers(),
|
|
537
|
+
body: JSON.stringify({ config: JSON.parse(els.configEditor.value) })
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
els.launchJob.addEventListener("click", async () => {
|
|
542
|
+
if (!state.selectedTemplateId || !state.selectedOperation) return;
|
|
543
|
+
const data = await request("/templates/" + state.selectedTemplateId + "/operations/" + state.selectedOperation, {
|
|
544
|
+
method: "POST",
|
|
545
|
+
headers: headers(),
|
|
546
|
+
body: JSON.stringify({
|
|
547
|
+
tracer: els.tracerInput.value.trim() || ("dev-" + Date.now()),
|
|
548
|
+
payload: JSON.parse(els.payloadEditor.value),
|
|
549
|
+
webhook_url: els.webhookInput.value.trim() || undefined
|
|
550
|
+
})
|
|
551
|
+
});
|
|
552
|
+
state.selectedJobId = data.job_id;
|
|
553
|
+
await refreshJobs();
|
|
554
|
+
await refreshJobDetail();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
els.loadJobs.addEventListener("click", refreshJobs);
|
|
558
|
+
els.loadTemplate.addEventListener("click", hydrateTemplateEditors);
|
|
559
|
+
|
|
560
|
+
renderSession();
|
|
561
|
+
renderTemplates();
|
|
562
|
+
hydrateTemplateEditors();
|
|
563
|
+
if (state.session) {
|
|
564
|
+
refreshProviderKeys().catch((error) => {
|
|
565
|
+
els.inspector.textContent = JSON.stringify({ error: error.message }, null, 2);
|
|
566
|
+
});
|
|
567
|
+
refreshJobs().catch((error) => {
|
|
568
|
+
els.inspector.textContent = JSON.stringify({ error: error.message }, null, 2);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
</script>
|
|
572
|
+
`,
|
|
573
|
+
style: `
|
|
574
|
+
.console-frame .frame-body {
|
|
575
|
+
min-height: 0;
|
|
576
|
+
overflow-y: auto;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.workspace {
|
|
580
|
+
display: grid;
|
|
581
|
+
gap: 18px;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.stat-value {
|
|
585
|
+
margin-top: 10px;
|
|
586
|
+
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
|
587
|
+
line-height: 0.9;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.template-grid,
|
|
591
|
+
.job-grid {
|
|
592
|
+
display: grid;
|
|
593
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
594
|
+
gap: 14px;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.template-card,
|
|
598
|
+
.job-card {
|
|
599
|
+
display: block;
|
|
600
|
+
width: 100%;
|
|
601
|
+
height: auto;
|
|
602
|
+
border-radius: 20px;
|
|
603
|
+
text-align: left;
|
|
604
|
+
padding: 18px;
|
|
605
|
+
background: rgba(255, 251, 244, 0.52);
|
|
606
|
+
color: var(--ink);
|
|
607
|
+
border: 1px solid rgba(102, 82, 43, 0.14);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.template-card.active {
|
|
611
|
+
border-color: rgba(118, 89, 38, 0.42);
|
|
612
|
+
box-shadow: 0 0 0 4px rgba(170, 123, 44, 0.12);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.template-title {
|
|
616
|
+
min-width: 0;
|
|
617
|
+
overflow-wrap: anywhere;
|
|
618
|
+
font-size: 1.5rem;
|
|
619
|
+
line-height: 1;
|
|
620
|
+
font-weight: 800;
|
|
621
|
+
letter-spacing: -0.04em;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.job-card .muted {
|
|
625
|
+
overflow-wrap: anywhere;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.template-desc,
|
|
629
|
+
.muted {
|
|
630
|
+
color: var(--muted);
|
|
631
|
+
line-height: 1.55;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.operation-list,
|
|
635
|
+
.ribbon {
|
|
636
|
+
display: flex;
|
|
637
|
+
flex-wrap: wrap;
|
|
638
|
+
gap: 8px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.ribbon .pill {
|
|
642
|
+
max-width: 100%;
|
|
643
|
+
min-width: 0;
|
|
644
|
+
height: auto;
|
|
645
|
+
flex-wrap: wrap;
|
|
646
|
+
padding-top: 6px;
|
|
647
|
+
padding-bottom: 6px;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.ribbon .pill strong {
|
|
651
|
+
min-width: 0;
|
|
652
|
+
overflow-wrap: anywhere;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.op {
|
|
656
|
+
padding: 6px 10px;
|
|
657
|
+
border: 1px solid var(--line);
|
|
658
|
+
border-radius: 999px;
|
|
659
|
+
background: rgba(170, 123, 44, 0.08);
|
|
660
|
+
font-size: 12px;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.timeline {
|
|
664
|
+
border-left: 1px solid var(--line);
|
|
665
|
+
margin-left: 6px;
|
|
666
|
+
padding-left: 18px;
|
|
667
|
+
display: grid;
|
|
668
|
+
gap: 16px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.event {
|
|
672
|
+
position: relative;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.event::before {
|
|
676
|
+
content: "";
|
|
677
|
+
position: absolute;
|
|
678
|
+
left: -24px;
|
|
679
|
+
top: 6px;
|
|
680
|
+
width: 10px;
|
|
681
|
+
height: 10px;
|
|
682
|
+
border-radius: 50%;
|
|
683
|
+
background: var(--accent);
|
|
684
|
+
box-shadow: 0 0 0 5px rgba(170, 123, 44, 0.14);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.event-head {
|
|
688
|
+
display: flex;
|
|
689
|
+
justify-content: space-between;
|
|
690
|
+
gap: 12px;
|
|
691
|
+
font-size: 13px;
|
|
692
|
+
color: var(--muted);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.event-msg {
|
|
696
|
+
margin-top: 6px;
|
|
697
|
+
font-size: 16px;
|
|
698
|
+
overflow-wrap: anywhere;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.status {
|
|
702
|
+
display: inline-block;
|
|
703
|
+
padding: 4px 10px;
|
|
704
|
+
border: 1px solid var(--line);
|
|
705
|
+
border-radius: 999px;
|
|
706
|
+
font-size: 11px;
|
|
707
|
+
text-transform: uppercase;
|
|
708
|
+
letter-spacing: 0.18em;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.status.succeeded { color: #0f8b8d; }
|
|
712
|
+
.status.failed { color: #b5532f; }
|
|
713
|
+
.status.running { color: #947124; }
|
|
714
|
+
|
|
715
|
+
.footer-note {
|
|
716
|
+
color: var(--muted);
|
|
717
|
+
font-size: 14px;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
@media (max-width: 1024px) {
|
|
721
|
+
.console-frame .frame-body {
|
|
722
|
+
overflow: visible;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
#configEditor,
|
|
726
|
+
#payloadEditor {
|
|
727
|
+
font-size: 16px;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
@media (max-width: 560px) {
|
|
732
|
+
.console-frame .topbar-actions .pill {
|
|
733
|
+
display: none;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
`
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
//# sourceMappingURL=dev-app-legacy.js.map
|