feat: add asset preferences, video research, and Remotion ad assets

- Add thumbs-down feedback modal and preference API endpoint
- Add AI UGC video platforms research doc
- Add ReflectAd Remotion composition with public flow assets
- Add gemini-ad-designer and poster-ad-designer pipeline skills
- Add research_reflect_v1.1 pipeline script

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-05-03 20:28:07 -05:00
parent b318798ca7
commit 807dfc539b
40 changed files with 3089 additions and 232 deletions
+48 -3
View File
@@ -12,6 +12,29 @@ interface ScannedFile {
metadata: string | null;
}
/** Truncate a JSON string to maxLen while keeping it valid JSON. */
function safeJsonTruncate(json: string, maxLen: number): string {
if (json.length <= maxLen) return json;
try {
const parsed = JSON.parse(json);
// Try to produce a shorter version by re-stringifying with key limits
const trimmed = JSON.stringify(parsed, (_key, value) => {
if (typeof value === "string" && value.length > 200) {
return value.slice(0, 200) + "…";
}
if (Array.isArray(value) && value.length > 5) {
return value.slice(0, 5);
}
return value;
});
if (trimmed.length <= maxLen) return trimmed;
// Still too long — return a minimal summary
return JSON.stringify({ _truncated: true, _originalLength: json.length });
} catch {
return JSON.stringify({ _truncated: true, _originalLength: json.length });
}
}
const FORMAT_TO_TYPE: Record<string, string> = {
png: "image",
jpg: "image",
@@ -65,13 +88,13 @@ function loadMetadata(fullPath: string, format: string): string | null {
if (Array.isArray(parsed)) {
return JSON.stringify({ captions: parsed.slice(0, 3), totalVariations: parsed.length });
}
return content.slice(0, 2000);
return safeJsonTruncate(content, 2000);
}
// For media files, look for adjacent JSON with same name
const jsonPath = fullPath.replace(/\.[^.]+$/, ".json");
if (existsSync(jsonPath)) {
return readFileSync(jsonPath, "utf-8").slice(0, 2000);
return safeJsonTruncate(readFileSync(jsonPath, "utf-8"), 2000);
}
// Look for manifest in same directory
@@ -85,7 +108,10 @@ function loadMetadata(fullPath: string, format: string): string | null {
const entry = manifest.find((e: { fileName?: string; file?: string }) =>
e.fileName === fileName || e.file === fileName
);
if (entry) return JSON.stringify(entry);
if (entry) {
// Ensure style field is preserved in metadata
return JSON.stringify(entry);
}
}
}
@@ -122,6 +148,25 @@ function scanDirectory(dir: string, baseDir: string): ScannedFile[] {
const ext = path.extname(entry).toLowerCase().slice(1);
if (!ext || ext === "gitkeep") continue;
// Only ingest deliverable files — skip source/build artifacts
const ASSET_EXTENSIONS = new Set([
"png", "jpg", "jpeg", "webp", "gif", // images
"mp4", "webm", // videos
]);
const CONTENT_EXTENSIONS = new Set([
"json", "md", "txt", // copy/scripts/research
]);
// Skip HTML source files, render scripts, and build tools
if (!ASSET_EXTENSIONS.has(ext) && !CONTENT_EXTENSIONS.has(ext)) continue;
// Skip known build/tool artifacts
const SKIP_FILES = new Set([
"tavily_search.mjs", "render_posters.mjs", "design_philosophy.md",
]);
if (SKIP_FILES.has(entry)) continue;
// Skip HTML source files in ads/ (they're build artifacts, not deliverables)
const relativePath0 = path.relative(baseDir, fullPath);
if (ext === "html" && relativePath0.includes("/ads/")) continue;
const relativePath = path.relative(baseDir, fullPath);
const type = inferTypeFromPath(relativePath, ext);
const metadata = loadMetadata(fullPath, ext);