feat: Felo 上传首页案例脚本
This commit is contained in:
parent
7ce3d8c8d5
commit
0e42d20199
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
const AUTHORIZATION = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMiJ9.2XLrkX3Fryw4c1Mn3Peqsp8JOMMOu25GP7yxl0gsIG4w9JjfUzlsharGdm8PDusK1fvwMRs5rc1p6LJe0cSg4w';
|
||||||
|
const API_URL = 'https://bops.felo.ai/api/home/case/type/add';
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
name: 'Logo 设计',
|
||||||
|
skill_id: 'logo-and-branding',
|
||||||
|
case_prompt: '请设计一个Logo,品牌名称是:[输入您的品牌名],所属行业是:[如:科技/餐饮/服装]。',
|
||||||
|
case_type: 'logo_design',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '品牌周边',
|
||||||
|
skill_id: 'logo-and-branding',
|
||||||
|
case_prompt: '基于我们的 Logo 设计一套品牌周边,用在[办公/生活/服饰]上',
|
||||||
|
case_type: 'brand_merchandise',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '亚马逊套图',
|
||||||
|
skill_id: 'ecommerce-product-image',
|
||||||
|
case_prompt: '用 [$name$] 的视觉风格,生成亚马逊 7 张套图(白底主图 + 信息图 + 生活场景图 + 细节图 + 多角度图),我的产品卖点是:[输入产品卖点]',
|
||||||
|
case_type: 'amazon_product_images',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '电商 Banner',
|
||||||
|
skill_id: 'ecommerce-product-image',
|
||||||
|
case_prompt: '生成宽幅 Banner 背景图。商品放在[左侧/右侧],卖点是:[输入产品卖点]',
|
||||||
|
case_type: 'ecommerce_banner',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '名片',
|
||||||
|
skill_id: 'marketing-materials',
|
||||||
|
case_prompt: '设计一套名片,我的要求是:[输入所属行业/职位/风格偏好]',
|
||||||
|
case_type: 'business_card',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '海报',
|
||||||
|
skill_id: 'marketing-materials',
|
||||||
|
case_prompt: '设计一张海报,我的要求是:[输入活动主题/核心卖点]',
|
||||||
|
case_type: 'poster',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '折页',
|
||||||
|
skill_id: 'marketing-materials',
|
||||||
|
case_prompt: '设计成一份[三折页宣传册/高级画册],用作活动宣传,活动基础信息为:[输入活动基础信息]',
|
||||||
|
case_type: 'brochure',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function post(payload, index) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
case_prompt: payload.case_prompt,
|
||||||
|
case_type: payload.case_type,
|
||||||
|
name: payload.name,
|
||||||
|
skill_id: payload.skill_id,
|
||||||
|
sort: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsed = new url.URL(API_URL);
|
||||||
|
const options = {
|
||||||
|
hostname: parsed.hostname,
|
||||||
|
path: parsed.pathname,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
'Authorization': AUTHORIZATION,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => (data += chunk));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve({ status: res.statusCode, body: JSON.parse(data) });
|
||||||
|
} catch {
|
||||||
|
resolve({ status: res.statusCode, body: data });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', reject);
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log(`开始提交 ${data.length} 条分类数据...\n`);
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const item = data[i];
|
||||||
|
process.stdout.write(`[${i + 1}/${data.length}] ${item.name} (${item.case_type}) ... `);
|
||||||
|
try {
|
||||||
|
const result = await post(item, i + 1);
|
||||||
|
if (result.status >= 200 && result.status < 300) {
|
||||||
|
console.log(`✓ ${result.status}`);
|
||||||
|
} else {
|
||||||
|
console.log(`✗ ${result.status}: ${JSON.stringify(result.body)}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`✗ 请求失败: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n完成。');
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
import { execFileSync } from 'child_process';
|
||||||
|
import readline from 'readline';
|
||||||
|
|
||||||
|
const AUTHORIZATION = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMiJ9.2XLrkX3Fryw4c1Mn3Peqsp8JOMMOu25GP7yxl0gsIG4w9JjfUzlsharGdm8PDusK1fvwMRs5rc1p6LJe0cSg4w';
|
||||||
|
const BASE_URL = 'https://bops.felo.ai';
|
||||||
|
|
||||||
|
const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.avif']);
|
||||||
|
|
||||||
|
// ── 交互输入 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function prompt(question) {
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ffmpeg 转换 ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
function convertToWebp(inputPath) {
|
||||||
|
const tmpFile = path.join(os.tmpdir(), `case_upload_${Date.now()}_${Math.random().toString(36).slice(2)}.webp`);
|
||||||
|
execFileSync('cwebp', ['-q', '80', '-resize', '800', '0', inputPath, '-o', tmpFile], { stdio: 'pipe' });
|
||||||
|
return tmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 上传图片 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function uploadFile(filePath) {
|
||||||
|
const formData = new FormData();
|
||||||
|
const fileData = fs.readFileSync(filePath);
|
||||||
|
const blob = new Blob([fileData], { type: 'image/webp' });
|
||||||
|
formData.append('file', blob, path.basename(filePath));
|
||||||
|
|
||||||
|
const res = await fetch(`${BASE_URL}/api/home/case/upload`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': AUTHORIZATION },
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
return { status: res.status, body };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 创建案例 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function createCase(caseTypeId, imageUrl, name, sort) {
|
||||||
|
const res = await fetch(`${BASE_URL}/api/home/case/create`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': AUTHORIZATION,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
case_type_id: caseTypeId,
|
||||||
|
image_url: imageUrl,
|
||||||
|
name,
|
||||||
|
sort: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
return { status: res.status, body };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 主流程 ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const caseTypeId = await prompt('请输入分类 ID (case_type_id): ');
|
||||||
|
const input = await prompt('请输入图片目录路径: ');
|
||||||
|
const absDir = path.resolve(input);
|
||||||
|
|
||||||
|
if (!fs.existsSync(absDir)) {
|
||||||
|
console.error(`目录不存在: ${absDir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(absDir)
|
||||||
|
.filter((f) => IMAGE_EXTS.has(path.extname(f).toLowerCase()))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.error('目录中未找到图片文件');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`找到 ${files.length} 张图片,开始处理...\n`);
|
||||||
|
|
||||||
|
const tmpFiles = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const inputPath = path.join(absDir, file);
|
||||||
|
console.log(`[${i + 1}/${files.length}] ${file}`);
|
||||||
|
|
||||||
|
// 1. 转换 WebP
|
||||||
|
let tmpPath;
|
||||||
|
try {
|
||||||
|
process.stdout.write(' → ffmpeg 转换... ');
|
||||||
|
tmpPath = convertToWebp(inputPath);
|
||||||
|
tmpFiles.push(tmpPath);
|
||||||
|
console.log('✓');
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`✗ 转换失败: ${err.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 上传
|
||||||
|
let imageUrl;
|
||||||
|
try {
|
||||||
|
process.stdout.write(' → 上传图片... ');
|
||||||
|
const uploadRes = await uploadFile(tmpPath);
|
||||||
|
console.log(`✓ HTTP ${uploadRes.status}`);
|
||||||
|
console.log(' upload res.data:', JSON.stringify(uploadRes.body, null, 2));
|
||||||
|
|
||||||
|
const body = uploadRes.body;
|
||||||
|
imageUrl = body?.data?.url ?? body?.data ?? body?.url ?? null;
|
||||||
|
if (!imageUrl) {
|
||||||
|
console.log(' ⚠ 无法自动提取 image_url,请确认上方打印的字段结构后修改脚本');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`✗ 上传失败: ${err.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 创建案例
|
||||||
|
try {
|
||||||
|
process.stdout.write(` → 创建案例 (sort=${i + 1})... `);
|
||||||
|
const createRes = await createCase(caseTypeId, imageUrl, path.basename(file, path.extname(file)), i + 1);
|
||||||
|
if (createRes.status >= 200 && createRes.status < 300) {
|
||||||
|
console.log(`✓ HTTP ${createRes.status}`);
|
||||||
|
} else {
|
||||||
|
console.log(`✗ HTTP ${createRes.status}: ${JSON.stringify(createRes.body)}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`✗ 创建失败: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const f of tmpFiles) {
|
||||||
|
try { fs.unlinkSync(f); } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('完成。');
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Loading…
Reference in New Issue