@mevdragon/vidfarm-devcli 0.2.8 → 0.2.9
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/.env.example +9 -0
- package/GETTING_STARTED.developers.md +1 -26
- package/README.md +3 -0
- package/SKILL.developer.md +327 -4
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +475 -0
- package/auto-create-templates/extractor-system-prompt.md +113 -0
- package/auto-create-templates/production-graph.schema.json +276 -0
- package/auto-create-templates/runbook.md +140 -0
- package/auto-create-templates/template-plan.schema.json +230 -0
- package/auto-create-templates/template-planner-prompt.md +75 -0
- package/dist/src/account-pages-legacy.js +9064 -0
- package/dist/src/account-pages.js +41 -620
- package/dist/src/app.js +6627 -244
- package/dist/src/cli.js +386 -26
- package/dist/src/config.js +12 -2
- package/dist/src/context.js +35 -35
- package/dist/src/db.js +1016 -53
- package/dist/src/dev-app-legacy.js +693 -0
- package/dist/src/dev-app.js +4 -904
- package/dist/src/domain.js +1 -1
- package/dist/src/editor-chat-history.js +72 -0
- package/dist/src/editor-chat.js +202 -0
- package/dist/src/frontend/flockposter-cache-store.js +116 -0
- package/dist/src/frontend/homepage-client.js +114 -0
- package/dist/src/frontend/homepage-shared.js +3 -0
- package/dist/src/frontend/homepage-store.js +27 -0
- package/dist/src/frontend/homepage-view.js +147 -0
- package/dist/src/frontend/page-runtime-client.js +100 -0
- package/dist/src/frontend/page-runtime-store.js +8 -0
- package/dist/src/frontend/template-editor-chat.js +2261 -0
- package/dist/src/homepage.js +695 -371
- package/dist/src/lib/template-style-options.js +95 -15
- package/dist/src/page-runtime.js +1 -0
- package/dist/src/page-shell.js +941 -0
- package/dist/src/primitive-context.js +163 -0
- package/dist/src/primitive-registry.js +340 -0
- package/dist/src/primitive-sdk.js +3 -0
- package/dist/src/primitives/remotion/html-image.js +28 -0
- package/dist/src/react-page-shell.js +34 -0
- package/dist/src/ready-post-schedule-component.js +1514 -0
- package/dist/src/registry.js +36 -17
- package/dist/src/runtime.js +6 -1
- package/dist/src/services/api-call-history.js +245 -0
- package/dist/src/services/auth.js +25 -3
- package/dist/src/services/billing.js +248 -9
- package/dist/src/services/chat-threads.js +88 -0
- package/dist/src/services/job-logs.js +14 -7
- package/dist/src/services/jobs.js +13 -2
- package/dist/src/services/providers.js +552 -133
- package/dist/src/services/rate-limits.js +236 -0
- package/dist/src/services/remotion.js +143 -16
- package/dist/src/services/storage.js +10 -8
- package/dist/src/services/template-certification.js +11 -2
- package/dist/src/services/template-loader.js +3 -1
- package/dist/src/services/template-sources.js +68 -3
- package/dist/src/template-editor-pages.js +2309 -0
- package/dist/src/template-editor-shell.js +1507 -0
- package/dist/src/worker.js +120 -22
- package/package.json +27 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +54 -0
- package/public/assets/page-runtime-client-app.js +68 -0
- package/dist/templates/vidfarm_template_0000/src/lib/images.js +0 -202
- package/dist/templates/vidfarm_template_0000/src/remotion/Root.js +0 -34
- package/dist/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/dist/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/dist/templates/vidfarm_template_0000/src/style-options.js +0 -51
- package/dist/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/dist/templates/vidfarm_template_0000/src/template.js +0 -1447
- package/templates/vidfarm_template_0000/README.md +0 -100
- package/templates/vidfarm_template_0000/SKILL.md +0 -225
- 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/package.json +0 -27
- package/templates/vidfarm_template_0000/research/preview/.gitkeep +0 -1
- package/templates/vidfarm_template_0000/research/source_notes.md +0 -7
- package/templates/vidfarm_template_0000/src/lib/images.js +0 -202
- package/templates/vidfarm_template_0000/src/lib/images.ts +0 -241
- package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
- package/templates/vidfarm_template_0000/src/remotion/Root.tsx +0 -75
- package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/templates/vidfarm_template_0000/src/remotion/index.tsx +0 -4
- package/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/templates/vidfarm_template_0000/src/sdk.ts +0 -140
- package/templates/vidfarm_template_0000/src/style-options.js +0 -51
- package/templates/vidfarm_template_0000/src/style-options.ts +0 -60
- package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/templates/vidfarm_template_0000/src/template-dna.ts +0 -15
- package/templates/vidfarm_template_0000/src/template.js +0 -1447
- package/templates/vidfarm_template_0000/src/template.ts +0 -2049
- package/templates/vidfarm_template_0000/template.config.json +0 -21
- package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +0 -31
- package/templates/vidfarm_template_0000/tsconfig.json +0 -19
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { config } from "./config.js";
|
|
2
|
+
import { database } from "./db.js";
|
|
3
|
+
import { createId } from "./lib/ids.js";
|
|
4
|
+
import { jobLogStore } from "./services/job-logs.js";
|
|
5
|
+
export function createPrimitiveJobContext(input) {
|
|
6
|
+
const prefix = input.storage.primitiveJobPrefix(input.primitive.id, input.customer.id, input.job.id);
|
|
7
|
+
const buildArtifactUrl = (storageKey, fallbackUrl) => fallbackUrl ?? input.storage.getPublicUrl(storageKey);
|
|
8
|
+
return {
|
|
9
|
+
env: config.isProduction ? "production" : "development",
|
|
10
|
+
customer: input.customer,
|
|
11
|
+
logger: {
|
|
12
|
+
debug(message, metadata = {}) {
|
|
13
|
+
jobLogStore.addEvent({
|
|
14
|
+
id: createId("evt"),
|
|
15
|
+
jobId: input.job.id,
|
|
16
|
+
level: "debug",
|
|
17
|
+
message,
|
|
18
|
+
metadata,
|
|
19
|
+
progress: null,
|
|
20
|
+
artifactKey: null
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
info(message, metadata = {}) {
|
|
24
|
+
jobLogStore.addEvent({
|
|
25
|
+
id: createId("evt"),
|
|
26
|
+
jobId: input.job.id,
|
|
27
|
+
level: "info",
|
|
28
|
+
message,
|
|
29
|
+
metadata,
|
|
30
|
+
progress: null,
|
|
31
|
+
artifactKey: null
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
warn(message, metadata = {}) {
|
|
35
|
+
jobLogStore.addEvent({
|
|
36
|
+
id: createId("evt"),
|
|
37
|
+
jobId: input.job.id,
|
|
38
|
+
level: "warn",
|
|
39
|
+
message,
|
|
40
|
+
metadata,
|
|
41
|
+
progress: null,
|
|
42
|
+
artifactKey: null
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
error(message, metadata = {}) {
|
|
46
|
+
jobLogStore.addEvent({
|
|
47
|
+
id: createId("evt"),
|
|
48
|
+
jobId: input.job.id,
|
|
49
|
+
level: "error",
|
|
50
|
+
message,
|
|
51
|
+
metadata,
|
|
52
|
+
progress: null,
|
|
53
|
+
artifactKey: null
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
progress(progress, message, metadata = {}) {
|
|
57
|
+
jobLogStore.addEvent({
|
|
58
|
+
id: createId("evt"),
|
|
59
|
+
jobId: input.job.id,
|
|
60
|
+
level: "info",
|
|
61
|
+
message,
|
|
62
|
+
metadata,
|
|
63
|
+
progress,
|
|
64
|
+
artifactKey: typeof metadata.artifactKey === "string" ? metadata.artifactKey : null
|
|
65
|
+
});
|
|
66
|
+
database.updateJobStatus({
|
|
67
|
+
id: input.job.id,
|
|
68
|
+
status: "running",
|
|
69
|
+
progress
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
storage: {
|
|
74
|
+
async putJson(key, value) {
|
|
75
|
+
const stored = await input.storage.putJson(`${prefix}/${key}`, value);
|
|
76
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
77
|
+
insertArtifact("json", stored.key, artifactUrl, {});
|
|
78
|
+
return { ...stored, url: artifactUrl };
|
|
79
|
+
},
|
|
80
|
+
async putText(key, value, contentType = "text/plain; charset=utf-8") {
|
|
81
|
+
const stored = await input.storage.putText(`${prefix}/${key}`, value, contentType);
|
|
82
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
83
|
+
insertArtifact("text", stored.key, artifactUrl, { contentType });
|
|
84
|
+
return { ...stored, url: artifactUrl };
|
|
85
|
+
},
|
|
86
|
+
async putBuffer(key, value, options = {}) {
|
|
87
|
+
const stored = await input.storage.putBuffer(`${prefix}/${key}`, value, options.contentType);
|
|
88
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
89
|
+
insertArtifact(options.kind ?? "binary", stored.key, artifactUrl, {
|
|
90
|
+
...(options.metadata ?? {}),
|
|
91
|
+
contentType: options.contentType ?? null
|
|
92
|
+
});
|
|
93
|
+
return { ...stored, url: artifactUrl };
|
|
94
|
+
},
|
|
95
|
+
getPublicUrl(key) {
|
|
96
|
+
return input.storage.getPublicUrl(`${prefix}/${key}`);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
billing: {
|
|
100
|
+
async record(record) {
|
|
101
|
+
await input.billing.record({
|
|
102
|
+
customerId: input.customer.id,
|
|
103
|
+
jobId: input.job.id,
|
|
104
|
+
tracer: input.job.tracer,
|
|
105
|
+
templateId: input.primitive.id,
|
|
106
|
+
type: record.type,
|
|
107
|
+
costUsd: record.costUsd,
|
|
108
|
+
chargeUsd: record.chargeUsd ?? Number((record.costUsd * 1.2).toFixed(4)),
|
|
109
|
+
costCenterSlug: record.costCenterSlug,
|
|
110
|
+
idempotencyKey: record.idempotencyKey,
|
|
111
|
+
occurredAtMs: record.occurredAtMs,
|
|
112
|
+
metadata: record.metadata
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
providers: {
|
|
117
|
+
async generateImage(request) {
|
|
118
|
+
return input.providers.generateImage({
|
|
119
|
+
customerId: input.customer.id,
|
|
120
|
+
jobId: input.job.id,
|
|
121
|
+
workerId: input.workerId,
|
|
122
|
+
...request
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
async editImage(request) {
|
|
126
|
+
return input.providers.editImage({
|
|
127
|
+
customerId: input.customer.id,
|
|
128
|
+
jobId: input.job.id,
|
|
129
|
+
workerId: input.workerId,
|
|
130
|
+
...request
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
async inpaintImage(request) {
|
|
134
|
+
return input.providers.inpaintImage({
|
|
135
|
+
customerId: input.customer.id,
|
|
136
|
+
jobId: input.job.id,
|
|
137
|
+
workerId: input.workerId,
|
|
138
|
+
...request
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
remotion: {
|
|
143
|
+
async renderStill(request) {
|
|
144
|
+
return input.remotion.renderStill({
|
|
145
|
+
...request,
|
|
146
|
+
outputKey: request.outputKey ? `${prefix}/${request.outputKey}` : undefined
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function insertArtifact(kind, storageKey, publicUrl, metadata) {
|
|
152
|
+
database.insertArtifact({
|
|
153
|
+
id: createId("art"),
|
|
154
|
+
jobId: input.job.id,
|
|
155
|
+
customerId: input.customer.id,
|
|
156
|
+
templateId: input.primitive.id,
|
|
157
|
+
kind,
|
|
158
|
+
storageKey,
|
|
159
|
+
publicUrl,
|
|
160
|
+
metadata
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import sharp from "sharp";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { definePrimitive } from "./primitive-sdk.js";
|
|
6
|
+
const imageProviderSchema = z.enum(["openai", "gemini", "openrouter"]);
|
|
7
|
+
const outputFormatSchema = z.enum(["png", "jpeg", "webp"]);
|
|
8
|
+
const aspectRatioSchema = z.enum(["1:1", "4:3", "3:4", "16:9", "9:16"]).optional();
|
|
9
|
+
const imageSizeSchema = z.enum(["1K", "2K", "4K"]).optional();
|
|
10
|
+
const generateImagePayloadSchema = z.object({
|
|
11
|
+
provider: imageProviderSchema.default("gemini"),
|
|
12
|
+
model: z.string().min(1).optional(),
|
|
13
|
+
prompt: z.string().min(3).max(8000),
|
|
14
|
+
prompt_attachments: z.array(z.string().url()).max(12).default([]),
|
|
15
|
+
aspect_ratio: aspectRatioSchema,
|
|
16
|
+
size: z.string().min(1).max(40).optional(),
|
|
17
|
+
image_size: imageSizeSchema,
|
|
18
|
+
output_format: outputFormatSchema.default("png")
|
|
19
|
+
});
|
|
20
|
+
const editImagePayloadSchema = z.object({
|
|
21
|
+
provider: imageProviderSchema.default("gemini"),
|
|
22
|
+
model: z.string().min(1).optional(),
|
|
23
|
+
source_image_url: z.string().url(),
|
|
24
|
+
instruction: z.string().min(3).max(8000),
|
|
25
|
+
reference_attachments: z.array(z.string().url()).max(12).default([]),
|
|
26
|
+
aspect_ratio: aspectRatioSchema,
|
|
27
|
+
size: z.string().min(1).max(40).optional(),
|
|
28
|
+
image_size: imageSizeSchema,
|
|
29
|
+
output_format: outputFormatSchema.default("png")
|
|
30
|
+
});
|
|
31
|
+
const inpaintImagePayloadSchema = z.object({
|
|
32
|
+
provider: imageProviderSchema.default("openai"),
|
|
33
|
+
model: z.string().min(1).optional(),
|
|
34
|
+
source_image_url: z.string().url(),
|
|
35
|
+
mask_url: z.string().url(),
|
|
36
|
+
instruction: z.string().min(3).max(8000),
|
|
37
|
+
reference_attachments: z.array(z.string().url()).max(12).default([]),
|
|
38
|
+
aspect_ratio: aspectRatioSchema,
|
|
39
|
+
size: z.string().min(1).max(40).optional(),
|
|
40
|
+
image_size: imageSizeSchema,
|
|
41
|
+
output_format: outputFormatSchema.default("png")
|
|
42
|
+
});
|
|
43
|
+
const renderHtmlImagePayloadSchema = z.object({
|
|
44
|
+
width: z.number().int().min(16).max(4096).default(1080),
|
|
45
|
+
height: z.number().int().min(16).max(4096).default(1920),
|
|
46
|
+
html: z.string().min(1).max(200_000),
|
|
47
|
+
css: z.string().max(200_000).optional(),
|
|
48
|
+
background_color: z.string().min(1).max(64).default("#ffffff"),
|
|
49
|
+
output_format: outputFormatSchema.default("png")
|
|
50
|
+
});
|
|
51
|
+
const PRIMITIVE_IMAGE_GENERATE_ID = "primitive:image_generate";
|
|
52
|
+
const PRIMITIVE_IMAGE_EDIT_ID = "primitive:image_edit";
|
|
53
|
+
const PRIMITIVE_IMAGE_INPAINT_ID = "primitive:image_inpaint";
|
|
54
|
+
const PRIMITIVE_IMAGE_RENDER_HTML_ID = "primitive:image_render_html";
|
|
55
|
+
const HTML_IMAGE_ENTRY_POINT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "primitives", "remotion", "html-image.tsx");
|
|
56
|
+
const HTML_IMAGE_COMPOSITION_ID = "primitive-html-image";
|
|
57
|
+
const imageGeneratePrimitive = definePrimitive({
|
|
58
|
+
id: PRIMITIVE_IMAGE_GENERATE_ID,
|
|
59
|
+
kind: "image_generate",
|
|
60
|
+
operations: {
|
|
61
|
+
run: {
|
|
62
|
+
description: "Generate a new image and persist it as a reusable platform URL.",
|
|
63
|
+
inputSchema: generateImagePayloadSchema,
|
|
64
|
+
workflow: "run"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
jobs: {
|
|
68
|
+
async run(ctx, input) {
|
|
69
|
+
const payload = generateImagePayloadSchema.parse(input);
|
|
70
|
+
ctx.logger.progress(0.12, "Generating primitive image");
|
|
71
|
+
const provider = payload.provider;
|
|
72
|
+
const model = payload.model ?? defaultImageModelForProvider(provider);
|
|
73
|
+
const generated = await ctx.providers.generateImage({
|
|
74
|
+
provider,
|
|
75
|
+
model,
|
|
76
|
+
prompt: payload.prompt,
|
|
77
|
+
promptAttachments: payload.prompt_attachments,
|
|
78
|
+
aspectRatio: payload.aspect_ratio,
|
|
79
|
+
size: payload.size,
|
|
80
|
+
imageSize: payload.image_size
|
|
81
|
+
});
|
|
82
|
+
const normalized = await normalizePrimitiveImageOutput(generated.bytes, payload.output_format);
|
|
83
|
+
const stored = await storePrimitiveImage(ctx, {
|
|
84
|
+
key: `output.${extensionForOutputFormat(payload.output_format)}`,
|
|
85
|
+
bytes: normalized.bytes,
|
|
86
|
+
contentType: normalized.contentType,
|
|
87
|
+
metadata: {
|
|
88
|
+
primitive: "image_generate",
|
|
89
|
+
provider,
|
|
90
|
+
model,
|
|
91
|
+
prompt: payload.prompt,
|
|
92
|
+
revisedPrompt: generated.revisedPrompt
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
ctx.logger.progress(1, "Primitive image generation complete");
|
|
96
|
+
return {
|
|
97
|
+
progress: 1,
|
|
98
|
+
output: buildImagePrimitiveOutput({
|
|
99
|
+
fileUrl: stored.url,
|
|
100
|
+
contentType: stored.contentType,
|
|
101
|
+
revisedPrompt: generated.revisedPrompt,
|
|
102
|
+
files: stored.url ? [stored.url] : []
|
|
103
|
+
})
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const imageEditPrimitive = definePrimitive({
|
|
109
|
+
id: PRIMITIVE_IMAGE_EDIT_ID,
|
|
110
|
+
kind: "image_edit",
|
|
111
|
+
operations: {
|
|
112
|
+
run: {
|
|
113
|
+
description: "Edit an existing image into a new reusable platform URL.",
|
|
114
|
+
inputSchema: editImagePayloadSchema,
|
|
115
|
+
workflow: "run"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
jobs: {
|
|
119
|
+
async run(ctx, input) {
|
|
120
|
+
const payload = editImagePayloadSchema.parse(input);
|
|
121
|
+
ctx.logger.progress(0.12, "Editing primitive image");
|
|
122
|
+
const provider = payload.provider;
|
|
123
|
+
const model = payload.model ?? defaultImageModelForProvider(provider);
|
|
124
|
+
const edited = await ctx.providers.editImage({
|
|
125
|
+
provider,
|
|
126
|
+
model,
|
|
127
|
+
sourceImageUrl: payload.source_image_url,
|
|
128
|
+
instruction: payload.instruction,
|
|
129
|
+
referenceAttachments: payload.reference_attachments,
|
|
130
|
+
aspectRatio: payload.aspect_ratio,
|
|
131
|
+
size: payload.size,
|
|
132
|
+
imageSize: payload.image_size
|
|
133
|
+
});
|
|
134
|
+
const normalized = await normalizePrimitiveImageOutput(edited.bytes, payload.output_format);
|
|
135
|
+
const stored = await storePrimitiveImage(ctx, {
|
|
136
|
+
key: `output.${extensionForOutputFormat(payload.output_format)}`,
|
|
137
|
+
bytes: normalized.bytes,
|
|
138
|
+
contentType: normalized.contentType,
|
|
139
|
+
metadata: {
|
|
140
|
+
primitive: "image_edit",
|
|
141
|
+
provider,
|
|
142
|
+
model,
|
|
143
|
+
sourceImageUrl: payload.source_image_url,
|
|
144
|
+
instruction: payload.instruction,
|
|
145
|
+
revisedPrompt: edited.revisedPrompt
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
ctx.logger.progress(1, "Primitive image edit complete");
|
|
149
|
+
return {
|
|
150
|
+
progress: 1,
|
|
151
|
+
output: buildImagePrimitiveOutput({
|
|
152
|
+
fileUrl: stored.url,
|
|
153
|
+
contentType: stored.contentType,
|
|
154
|
+
revisedPrompt: edited.revisedPrompt,
|
|
155
|
+
derivedFromUrl: payload.source_image_url,
|
|
156
|
+
files: stored.url ? [stored.url] : []
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
const imageInpaintPrimitive = definePrimitive({
|
|
163
|
+
id: PRIMITIVE_IMAGE_INPAINT_ID,
|
|
164
|
+
kind: "image_inpaint",
|
|
165
|
+
operations: {
|
|
166
|
+
run: {
|
|
167
|
+
description: "Inpaint a masked region of an existing image into a new reusable platform URL.",
|
|
168
|
+
inputSchema: inpaintImagePayloadSchema,
|
|
169
|
+
workflow: "run"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
jobs: {
|
|
173
|
+
async run(ctx, input) {
|
|
174
|
+
const payload = inpaintImagePayloadSchema.parse(input);
|
|
175
|
+
ctx.logger.progress(0.12, "Inpainting primitive image");
|
|
176
|
+
const provider = payload.provider;
|
|
177
|
+
const model = payload.model ?? defaultImageModelForProvider(provider);
|
|
178
|
+
const edited = await ctx.providers.inpaintImage({
|
|
179
|
+
provider,
|
|
180
|
+
model,
|
|
181
|
+
sourceImageUrl: payload.source_image_url,
|
|
182
|
+
maskUrl: payload.mask_url,
|
|
183
|
+
instruction: payload.instruction,
|
|
184
|
+
referenceAttachments: payload.reference_attachments,
|
|
185
|
+
aspectRatio: payload.aspect_ratio,
|
|
186
|
+
size: payload.size,
|
|
187
|
+
imageSize: payload.image_size
|
|
188
|
+
});
|
|
189
|
+
const normalized = await normalizePrimitiveImageOutput(edited.bytes, payload.output_format);
|
|
190
|
+
const stored = await storePrimitiveImage(ctx, {
|
|
191
|
+
key: `output.${extensionForOutputFormat(payload.output_format)}`,
|
|
192
|
+
bytes: normalized.bytes,
|
|
193
|
+
contentType: normalized.contentType,
|
|
194
|
+
metadata: {
|
|
195
|
+
primitive: "image_inpaint",
|
|
196
|
+
provider,
|
|
197
|
+
model,
|
|
198
|
+
sourceImageUrl: payload.source_image_url,
|
|
199
|
+
maskUrl: payload.mask_url,
|
|
200
|
+
instruction: payload.instruction,
|
|
201
|
+
revisedPrompt: edited.revisedPrompt
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
ctx.logger.progress(1, "Primitive image inpaint complete");
|
|
205
|
+
return {
|
|
206
|
+
progress: 1,
|
|
207
|
+
output: buildImagePrimitiveOutput({
|
|
208
|
+
fileUrl: stored.url,
|
|
209
|
+
contentType: stored.contentType,
|
|
210
|
+
revisedPrompt: edited.revisedPrompt,
|
|
211
|
+
derivedFromUrl: payload.source_image_url,
|
|
212
|
+
maskUrl: payload.mask_url,
|
|
213
|
+
files: stored.url ? [stored.url] : []
|
|
214
|
+
})
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
const imageRenderHtmlPrimitive = definePrimitive({
|
|
220
|
+
id: PRIMITIVE_IMAGE_RENDER_HTML_ID,
|
|
221
|
+
kind: "image_render_html",
|
|
222
|
+
operations: {
|
|
223
|
+
run: {
|
|
224
|
+
description: "Render an HTML-driven Remotion still image into a reusable platform URL.",
|
|
225
|
+
inputSchema: renderHtmlImagePayloadSchema,
|
|
226
|
+
workflow: "run"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
jobs: {
|
|
230
|
+
async run(ctx, input) {
|
|
231
|
+
const payload = renderHtmlImagePayloadSchema.parse(input);
|
|
232
|
+
ctx.logger.progress(0.18, "Rendering primitive HTML image");
|
|
233
|
+
const rendered = await ctx.remotion.renderStill({
|
|
234
|
+
compositionId: HTML_IMAGE_COMPOSITION_ID,
|
|
235
|
+
entryPoint: HTML_IMAGE_ENTRY_POINT,
|
|
236
|
+
imageFormat: payload.output_format,
|
|
237
|
+
outputKey: `output.${extensionForOutputFormat(payload.output_format)}`,
|
|
238
|
+
inputProps: {
|
|
239
|
+
width: payload.width,
|
|
240
|
+
height: payload.height,
|
|
241
|
+
html: payload.html,
|
|
242
|
+
css: payload.css ?? "",
|
|
243
|
+
backgroundColor: payload.background_color
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
ctx.logger.progress(1, "Primitive HTML image render complete");
|
|
247
|
+
return {
|
|
248
|
+
progress: 1,
|
|
249
|
+
output: {
|
|
250
|
+
files: rendered.outputUrl ? [rendered.outputUrl] : [],
|
|
251
|
+
primary_file_url: rendered.outputUrl,
|
|
252
|
+
image: {
|
|
253
|
+
file_url: rendered.outputUrl,
|
|
254
|
+
content_type: contentTypeForOutputFormat(payload.output_format),
|
|
255
|
+
width: payload.width,
|
|
256
|
+
height: payload.height
|
|
257
|
+
},
|
|
258
|
+
render: rendered
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
class PrimitiveRegistry {
|
|
265
|
+
definitions = new Map([
|
|
266
|
+
[imageGeneratePrimitive.id, imageGeneratePrimitive],
|
|
267
|
+
[imageEditPrimitive.id, imageEditPrimitive],
|
|
268
|
+
[imageInpaintPrimitive.id, imageInpaintPrimitive],
|
|
269
|
+
[imageRenderHtmlPrimitive.id, imageRenderHtmlPrimitive]
|
|
270
|
+
]);
|
|
271
|
+
get(id) {
|
|
272
|
+
return this.definitions.get(id) ?? null;
|
|
273
|
+
}
|
|
274
|
+
list() {
|
|
275
|
+
return [...this.definitions.values()];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export const primitiveRegistry = new PrimitiveRegistry();
|
|
279
|
+
async function storePrimitiveImage(ctx, input) {
|
|
280
|
+
const stored = await ctx.storage.putBuffer(input.key, input.bytes, {
|
|
281
|
+
contentType: input.contentType,
|
|
282
|
+
kind: "image",
|
|
283
|
+
metadata: input.metadata
|
|
284
|
+
});
|
|
285
|
+
return { ...stored, contentType: input.contentType };
|
|
286
|
+
}
|
|
287
|
+
function buildImagePrimitiveOutput(input) {
|
|
288
|
+
return {
|
|
289
|
+
files: input.files,
|
|
290
|
+
primary_file_url: input.fileUrl,
|
|
291
|
+
image: {
|
|
292
|
+
file_url: input.fileUrl,
|
|
293
|
+
content_type: input.contentType,
|
|
294
|
+
...(input.revisedPrompt !== undefined ? { revised_prompt: input.revisedPrompt } : {}),
|
|
295
|
+
...(input.derivedFromUrl ? { derived_from_url: input.derivedFromUrl } : {}),
|
|
296
|
+
...(input.maskUrl ? { mask_url: input.maskUrl } : {})
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function defaultImageModelForProvider(provider) {
|
|
301
|
+
switch (provider) {
|
|
302
|
+
case "openai":
|
|
303
|
+
return "gpt-image-1";
|
|
304
|
+
case "openrouter":
|
|
305
|
+
return "google/gemini-2.5-flash-image-preview";
|
|
306
|
+
default:
|
|
307
|
+
return "gemini-2.5-flash-image";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function extensionForOutputFormat(format) {
|
|
311
|
+
return format === "jpeg" ? "jpg" : format;
|
|
312
|
+
}
|
|
313
|
+
function contentTypeForOutputFormat(format, fallback) {
|
|
314
|
+
if (format === "jpeg") {
|
|
315
|
+
return "image/jpeg";
|
|
316
|
+
}
|
|
317
|
+
if (format === "webp") {
|
|
318
|
+
return "image/webp";
|
|
319
|
+
}
|
|
320
|
+
return fallback && fallback !== "image/svg+xml" ? fallback : "image/png";
|
|
321
|
+
}
|
|
322
|
+
async function normalizePrimitiveImageOutput(bytes, format) {
|
|
323
|
+
const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
324
|
+
if (format === "jpeg") {
|
|
325
|
+
return {
|
|
326
|
+
bytes: await sharp(buffer).jpeg({ quality: 92 }).toBuffer(),
|
|
327
|
+
contentType: "image/jpeg"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (format === "webp") {
|
|
331
|
+
return {
|
|
332
|
+
bytes: await sharp(buffer).webp({ quality: 92 }).toBuffer(),
|
|
333
|
+
contentType: "image/webp"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
bytes: await sharp(buffer).png().toBuffer(),
|
|
338
|
+
contentType: "image/png"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AbsoluteFill, Composition } from "remotion";
|
|
3
|
+
const defaultProps = {
|
|
4
|
+
width: 1080,
|
|
5
|
+
height: 1920,
|
|
6
|
+
html: "<div>Empty primitive HTML image.</div>",
|
|
7
|
+
css: "",
|
|
8
|
+
backgroundColor: "#ffffff"
|
|
9
|
+
};
|
|
10
|
+
const HtmlImageComposition = ({ html, css = "", backgroundColor = "#ffffff" }) => {
|
|
11
|
+
return (_jsxs(AbsoluteFill, { style: {
|
|
12
|
+
backgroundColor,
|
|
13
|
+
overflow: "hidden",
|
|
14
|
+
fontFamily: "Arial, sans-serif"
|
|
15
|
+
}, children: [css ? _jsx("style", { dangerouslySetInnerHTML: { __html: css } }) : null, _jsx("div", { style: {
|
|
16
|
+
width: "100%",
|
|
17
|
+
height: "100%"
|
|
18
|
+
}, dangerouslySetInnerHTML: { __html: html } })] }));
|
|
19
|
+
};
|
|
20
|
+
export const PrimitiveHtmlImageRoot = () => {
|
|
21
|
+
return (_jsx(Composition, { id: "primitive-html-image", component: HtmlImageComposition, width: 1080, height: 1920, fps: 30, durationInFrames: 1, defaultProps: defaultProps, calculateMetadata: ({ props }) => ({
|
|
22
|
+
width: typeof props.width === "number" ? Math.max(1, Math.round(props.width)) : 1080,
|
|
23
|
+
height: typeof props.height === "number" ? Math.max(1, Math.round(props.height)) : 1920,
|
|
24
|
+
durationInFrames: 1,
|
|
25
|
+
fps: 30
|
|
26
|
+
}) }));
|
|
27
|
+
};
|
|
28
|
+
export default PrimitiveHtmlImageRoot;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
+
import { escapeHtml } from "./page-shell.js";
|
|
4
|
+
function extractTagContent(documentHtml, tagName) {
|
|
5
|
+
const match = documentHtml.match(new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, "i"));
|
|
6
|
+
return match?.[1] ?? "";
|
|
7
|
+
}
|
|
8
|
+
export function parseLegacyDocument(documentHtml) {
|
|
9
|
+
return {
|
|
10
|
+
title: extractTagContent(documentHtml, "title"),
|
|
11
|
+
bodyHtml: extractTagContent(documentHtml, "body"),
|
|
12
|
+
styleText: extractTagContent(documentHtml, "style")
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function ReactDocument(input) {
|
|
16
|
+
const runtimeBoot = input.runtimeBoot ?? {
|
|
17
|
+
pageKind: "legacy",
|
|
18
|
+
title: input.title || "VidFarm AI"
|
|
19
|
+
};
|
|
20
|
+
return (_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: input.title }), _jsx("link", { rel: "icon", href: "/assets/favicon.ico", sizes: "any" }), _jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" }), _jsx("link", { href: "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Manrope:wght@400;500;600;700;800&display=swap", rel: "stylesheet" }), _jsx("style", { dangerouslySetInnerHTML: { __html: input.styleText } })] }), _jsxs("body", { children: [_jsx("div", { dangerouslySetInnerHTML: { __html: input.bodyHtml } }), _jsx("div", { id: "page-runtime-root", hidden: true }), _jsx("script", { id: "page-runtime-boot", type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(runtimeBoot).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026") } }), _jsx("script", { type: "module", src: "/assets/page-runtime-client-app.js" })] })] }));
|
|
21
|
+
}
|
|
22
|
+
export function renderParsedDocument(input) {
|
|
23
|
+
return `<!doctype html>${renderToStaticMarkup(_jsx(ReactDocument, { ...input }))}`;
|
|
24
|
+
}
|
|
25
|
+
export function renderLegacyPageAsReact(documentHtml, runtimeBoot) {
|
|
26
|
+
const parsed = parseLegacyDocument(documentHtml);
|
|
27
|
+
const safeTitle = parsed.title ? parsed.title : escapeHtml("VidFarm AI");
|
|
28
|
+
return renderParsedDocument({
|
|
29
|
+
title: safeTitle,
|
|
30
|
+
bodyHtml: parsed.bodyHtml,
|
|
31
|
+
styleText: parsed.styleText,
|
|
32
|
+
runtimeBoot
|
|
33
|
+
});
|
|
34
|
+
}
|