feat(models): add targeted VIP98K nano-banana-2.1 synchronization
This commit is contained in:
parent
c0cbc34911
commit
3750e89ed1
136
scripts/sync-vip98k-nano-banana-21.ts
Normal file
136
scripts/sync-vip98k-nano-banana-21.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import dotenv from "dotenv";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import postgres from "postgres";
|
||||
import {
|
||||
buildVip98kModelPresets,
|
||||
fetchVip98kCatalog,
|
||||
} from "../lib/ai/vip98k-models";
|
||||
|
||||
const MODEL_ID = "nano-banana-2.1";
|
||||
dotenv.config({ path: ".env.local", quiet: true });
|
||||
|
||||
async function main() {
|
||||
if (!process.env.POSTGRES_URL) throw new Error("POSTGRES_URL is required");
|
||||
const sql = postgres(process.env.POSTGRES_URL, { max: 1, prepare: false });
|
||||
try {
|
||||
const providers = await sql`
|
||||
select id, "baseUrl", "apiKey" from "AiProvider"
|
||||
where type = 'vip98k' and "isEnabled" = true
|
||||
`;
|
||||
const candidates = providers.filter(
|
||||
(provider) => new URL(provider.baseUrl).hostname === "api.vip98k.com",
|
||||
);
|
||||
if (candidates.length !== 1) {
|
||||
throw new Error("Expected exactly one enabled api.vip98k.com provider");
|
||||
}
|
||||
const provider = candidates[0]!;
|
||||
const catalog = await fetchVip98kCatalog({
|
||||
baseURL: provider.baseUrl,
|
||||
apiKey: provider.apiKey,
|
||||
});
|
||||
const model = catalog.find(
|
||||
(item) => (item.modelId || item.id || item.name) === MODEL_ID,
|
||||
);
|
||||
if (!model) throw new Error("Model is absent from the authorized catalog");
|
||||
const preset = buildVip98kModelPresets([model])[0];
|
||||
if (!preset || preset.isHidden || !preset.supportsVision) {
|
||||
throw new Error("Published model does not qualify for reference-image generation");
|
||||
}
|
||||
|
||||
// Use the published asynchronous contract for both text and reference input.
|
||||
// This preserves native resolution and aspect ratio instead of prompt hints.
|
||||
const metadata = preset.capabilities.vip98k as {
|
||||
invocation: Record<string, unknown>;
|
||||
invocations: Array<{
|
||||
kind: string;
|
||||
path: string;
|
||||
requestExample: Record<string, unknown>;
|
||||
inputSchema?: { properties?: Record<string, { enum?: string[] }> };
|
||||
}>;
|
||||
};
|
||||
const invocation = metadata.invocations.find(
|
||||
(item) => item.kind === "task" && item.path === "/extensions/v1/tasks",
|
||||
);
|
||||
const fields = invocation?.inputSchema?.properties;
|
||||
if (
|
||||
!invocation || !fields?.file_urls || !fields.aspect_ratio ||
|
||||
!["1K", "2K", "4K"].every((size) => fields.resolution?.enum?.includes(size))
|
||||
) {
|
||||
throw new Error("Expected asynchronous multi-reference and 1K/2K/4K contract");
|
||||
}
|
||||
const taskMetadata = { ...metadata, invocation, invocations: [invocation] };
|
||||
const capabilities = { ...preset.capabilities, vip98k: taskMetadata };
|
||||
const imageGenConfig = {
|
||||
...preset.imageGenConfig,
|
||||
supportsQuality: false,
|
||||
vip98k: taskMetadata,
|
||||
};
|
||||
const { quality: _quality, ...defaultParams } = preset.defaultParams;
|
||||
const existing = await sql`
|
||||
select * from "AiModel"
|
||||
where "providerId" = ${provider.id} and "modelId" = ${MODEL_ID}
|
||||
`;
|
||||
if (existing.length > 1) throw new Error("Duplicate model rows require reconciliation");
|
||||
const summary = {
|
||||
modelId: MODEL_ID,
|
||||
providerId: provider.id,
|
||||
apply: process.argv.includes("--apply"),
|
||||
exists: existing.length === 1,
|
||||
sizes: capabilities.supportedImageSizes,
|
||||
supportsReferenceImages: preset.supportsVision,
|
||||
invocationPath: invocation.path,
|
||||
};
|
||||
if (!summary.apply) return console.log(JSON.stringify(summary, null, 2));
|
||||
|
||||
const backupIndex = process.argv.indexOf("--backup-dir");
|
||||
if (backupIndex < 0 || !process.argv[backupIndex + 1]) {
|
||||
throw new Error("--backup-dir is required when applying configuration");
|
||||
}
|
||||
const backupDir = process.argv[backupIndex + 1]!;
|
||||
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
||||
writeFileSync(join(backupDir, `model-before-${Date.now()}.json`), JSON.stringify(existing), { mode: 0o600 });
|
||||
const asJson = (value: unknown) => sql.json(JSON.parse(JSON.stringify(value)));
|
||||
const id = await sql.begin(async (tx) => {
|
||||
await tx`select pg_advisory_xact_lock(hashtext(${provider.id + ":" + MODEL_ID}))`;
|
||||
const rows = await tx`
|
||||
select id from "AiModel"
|
||||
where "providerId" = ${provider.id} and "modelId" = ${MODEL_ID}
|
||||
for update
|
||||
`;
|
||||
if (rows.length > 1) throw new Error("Duplicate model rows");
|
||||
if (!rows.length) {
|
||||
const [created] = await tx`
|
||||
insert into "AiModel" (
|
||||
"providerId", "modelId", "displayName", description,
|
||||
"supportsVision", "supportsImageGen", "isEnabled", "isHidden",
|
||||
"isDefault", "sortOrder", capabilities, "defaultParams", "imageGenConfig"
|
||||
) values (
|
||||
${provider.id}, ${MODEL_ID}, ${preset.displayName}, ${preset.description},
|
||||
true, true, true, false, false, 106,
|
||||
${asJson(capabilities)}, ${asJson(defaultParams)}, ${asJson(imageGenConfig)}
|
||||
) returning id
|
||||
`;
|
||||
return created!.id;
|
||||
}
|
||||
await tx`
|
||||
update "AiModel" set
|
||||
"displayName" = ${preset.displayName}, description = ${preset.description},
|
||||
"supportsVision" = true, "supportsImageGen" = true,
|
||||
"isEnabled" = true, "isHidden" = false,
|
||||
capabilities = ${asJson(capabilities)}, "defaultParams" = ${asJson(defaultParams)},
|
||||
"imageGenConfig" = ${asJson(imageGenConfig)}, "updatedAt" = now()
|
||||
where id = ${rows[0]!.id}
|
||||
`;
|
||||
return rows[0]!.id;
|
||||
});
|
||||
console.log(JSON.stringify({ ...summary, id, enabled: true, visible: true }, null, 2));
|
||||
} finally {
|
||||
await sql.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : "Model synchronization failed");
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user