ai-chatbot/scripts/rewrite-agent-workflow-preview-image-urls.ts

196 lines
5.3 KiB
TypeScript

import { config as loadEnv } from "dotenv";
import { eq } from "drizzle-orm";
import { mkdir, writeFile } from "node:fs/promises";
import Module from "node:module";
import path from "node:path";
loadEnv({ path: ".env.local" });
const moduleLoader = Module as typeof Module & {
_load: (request: string, parent: NodeModule | null, isMain: boolean) => unknown;
};
const originalLoad = moduleLoader._load;
moduleLoader._load = function patchedServerOnlyLoad(
this: typeof moduleLoader,
request,
parent,
isMain
) {
if (request === "server-only") {
return {};
}
return originalLoad.call(this, request, parent, isMain);
};
const PREVIEW_WIDTH = 230;
const PREVIEW_QUALITY = 76;
const dryRun = process.argv.includes("--dry-run");
function normalizeUrl(value: unknown): string {
return String(value || "").trim();
}
function isAbsoluteHttpUrl(value: string) {
return /^https?:\/\//i.test(value);
}
function isUnsupportedPreviewSource(value: string) {
return !value || value.startsWith("data:") || value.startsWith("blob:");
}
function withoutKnownImageProcess(url: URL) {
const next = new URL(url.toString());
next.searchParams.delete("x-oss-process");
const rawSearch = next.search.replace(/^\?/, "");
if (/^imageView2\//i.test(rawSearch)) {
next.search = "";
}
return next;
}
function buildOptimizedPreviewUrl(source: string): string | null {
const normalized = normalizeUrl(source);
if (!isAbsoluteHttpUrl(normalized) || isUnsupportedPreviewSource(normalized)) {
return null;
}
let url: URL;
try {
url = new URL(normalized);
} catch {
return null;
}
const host = url.hostname.toLowerCase();
const sourceUrl = withoutKnownImageProcess(url);
if (host.includes("aliyuncs.com") || host.includes("oss-")) {
sourceUrl.searchParams.set(
"x-oss-process",
`image/resize,w_${PREVIEW_WIDTH}/quality,q_${PREVIEW_QUALITY}/format,webp`
);
return sourceUrl.toString();
}
if (host.includes("qiniu") || host.includes("clouddn") || host.includes("qiniucdn")) {
const separator = sourceUrl.search ? "&" : "?";
return `${sourceUrl.toString()}${separator}imageView2/2/w/${PREVIEW_WIDTH}/q/${PREVIEW_QUALITY}/format/webp`;
}
return null;
}
function visitOptions(options: any[], visitor: (option: any) => void) {
for (const option of options || []) {
visitor(option);
visitOptions(option.children || [], visitor);
}
}
function visitSteps(steps: any[], visitor: (option: any) => void) {
for (const step of steps || []) {
visitOptions(step.options || [], visitor);
visitSteps(step.children || [], visitor);
}
}
function createBackupPath() {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
return path.join(
process.cwd(),
"backups",
`agent-workflow-ui-config-before-url-rewrite-${timestamp}.json`
);
}
async function main() {
const dbModule = await import("../lib/db");
const schemaModule = await import("../lib/db/schema");
const workflowConfig = await import("../lib/agents/workflow-ui-config");
const { db } = dbModule;
const { systemSettings } = schemaModule;
const [setting] = await db
.select({ value: systemSettings.value })
.from(systemSettings)
.where(eq(systemSettings.key, workflowConfig.AGENT_WORKFLOW_UI_CONFIG_KEY))
.limit(1);
if (!setting?.value) {
console.log("No agent workflow UI config found.");
return;
}
const normalized = workflowConfig.normalizeAgentWorkflowUiConfig(setting.value);
const nextConfig = structuredClone(normalized);
const summary = {
total: 0,
rewritten: 0,
skipped: 0,
unsupported: 0,
};
visitSteps(nextConfig.steps || [], (option) => {
summary.total += 1;
const currentPreview = normalizeUrl(option.previewImageUrl);
const preferredSource = normalizeUrl(option.previewImageSourceUrl);
const source = preferredSource || currentPreview;
const optimized =
buildOptimizedPreviewUrl(source) ||
(preferredSource && currentPreview !== preferredSource
? buildOptimizedPreviewUrl(currentPreview)
: null);
if (!currentPreview || !source) {
summary.skipped += 1;
return;
}
if (!optimized) {
summary.unsupported += 1;
return;
}
if (currentPreview === optimized && option.previewImageSourceUrl === source) {
summary.skipped += 1;
return;
}
option.previewImageUrl = optimized;
option.previewImageSourceUrl = source;
summary.rewritten += 1;
});
console.log(JSON.stringify({ dryRun, summary }, null, 2));
if (dryRun || summary.rewritten === 0) {
return;
}
const backupPath = createBackupPath();
await mkdir(path.dirname(backupPath), { recursive: true });
await writeFile(
backupPath,
JSON.stringify(
{
key: workflowConfig.AGENT_WORKFLOW_UI_CONFIG_KEY,
backedUpAt: new Date().toISOString(),
value: setting.value,
},
null,
2
)
);
console.log(`Backup written: ${backupPath}`);
await db
.update(systemSettings)
.set({
value: nextConfig,
updatedAt: new Date(),
})
.where(eq(systemSettings.key, workflowConfig.AGENT_WORKFLOW_UI_CONFIG_KEY));
console.log(`Rewritten ${summary.rewritten} agent workflow preview image URLs.`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});